How to restrict access to mutable or immutable methods?
In a new Java project I try to apply as much best practices as possible. The one I'm having problems with is immutability. Although I understood the concept and already built some immutable classes I now came to a class where I think it's more appropriate t开发者_运维百科o do it as a mutable class.
The main problem is that I want to hide the mutable parts of the class in some cases so that in my case the view layer of MVC can't directly modify objects but has to go through its controller.
I thought of 2 ways to do it:
Create an interface "Thing" which has all immutable methods in it (read-only) and create an interface "MutableThing" which has the setters.
Put all methods in one interface and restrict access to mutating methods by wrapping the object like the Collections.unmodifiableList(obj) method so that exceptions are thrown when accessing mutating methods.
I would prefer the first one because I think it's cleaner and more well designed but I have one problem with that. I have addListener(l) and removeListener(l) in the "Thing" interface so the view layer can register itself as a listener to some model objects. But with these two methods in the "Thing" interface it just doesn't make sense on its own anymore. Why would an interface have the ability to register listeners which signal data changes if there is no method to actually change data? I could put these methods in the "MutableThing" interface but the view layer only has access to the "Thing" interface and couldn't register itself as listener.
The point why this isn't working is just because of the listeners, is the view layer actually responsible for registering itself as listener on the model? If the controller could do it somehow (which has access to the "MutableThing") then there wouldn't be a problem but I wouldn't know how to realize it.
What would you suggest?
Thanks!
Why would an interface have the ability to register listeners which signal data changes if there is no method to actually change data?
All the interface states is that you can retrieve the following values. It does not imply that the object is immutable. When the object is mutable it is IMO a good idea to make sure everyone knows it as immutable objects have some properties that your object might not have (thread safe, safe to cache results etc). Your object is mutable. You should not pretend it is immutable. As such go with 1 and have add/removes on the Thing interface.
I'd go for a modified first attempt.
You may define your immutables by an interface, which contains all the getters. A mutable class implements the Immutable interface and contains all the required set methods without having these specified by an interface. Within your code you only access instances via the Immutable interface. The places in your code, where you actually need to create or modify the classes, you (exclusively) refer to those mutable classes by simply casting or by making them visible by putting those modifying implementations in the same package as a the mutable classes. A bundle concept like in osgi/eclipse could be helpful here, because you find support in hiding implementations etc.
Additionally you may use a seal() method to seal instances once you have created (and modified them). Whenever a set-method is called, you check, whether the instance isn't sealed. This might help to prevent modifications (or when working in a big team).
The right approach is generally to have three classes and/or interfaces: ReadableFoo, ImmutableFoo, and MutableFoo. The latter two derive independently from the first, meaning that either may be used when a ReadableFoo is required, but only an ImmutableFoo can be used when an ImmutableFoo is required, and likewise with MutableFoo. It may be helpful for ReadableFoo to contain methods for CloneAsMutable, AsMutable, and AsImmutable. CloneAsMutable will always create a new mutable object with properties copied from the original. Calling AsMutable and AsImmutable on a ReadableFoo will either return the original object (if it was the desired type) or a new object with data copied from the original (if it wasn't).
In my opinion, both of the two possibilities that you mentioned are valid ways to solve the problem. With regard to the first method: I assume you mean that interface MutableThing
extends interface Thing
to add the mutator methods.
With regard to the listener stuff: There are ofcourse several ways to do this. You could separate this from the Thing
and MutableThing
interface, for example by using Java's java.util.Observable
class and java.util.Observer
interface (or something similar you write yourself - those classes are not type-safe, because they're from before generics were added to Java).
精彩评论