So I’m finishing up refactoring some code to remove a number of previously-mutable objects and add a better generic processing for all the classes in the domain. Just as I thought I was finishing I eralized that there is one sub-class that has some additional state.
The additional state is a link to other classes that are used as part of the logic for knowing when new domain objects will be created, deleted, or modified. However, this sub-class is only created at bootup, or when someone runs a command to re-read and update to new configuration files. I know that this object will always be created before any of the other objects in my domain that are dependent on it are, such that anyone pointing to this object are all gaurenteed to point to the same instance. I effectively have the Memorization pattern by an accident of how the structure is created.
I could refactor away the mutability; but it would require a bit of work modifying bootup logic that I would prefer to avoid. Or I could change the has and equals methods to ignore this one set of mutable values so my model will treat this object exactly like it’s immutable parent and trust that my knowledge of the method it is constructed prevent me from aliasing issues when I do try to use it’s mutable traits.
So how ‘wrong’ is it to bend my contract for thie class this way be?
1
There’s nothing inherently wrong with mutable subclasses provided you don’t make assumptions about mutability in other parts of your code.
As an example, the Foundation framework that’s part of Cocoa and Cocoa Touch frameworks (MacOS X and iOS respectively) has a number of immutable data containers that have mutable subclasses. NSMutableArray
is a mutable subclass of the immutable NSArray
, NSMutableDictionary
is a mutable subclass of the immutable NSDictionary
, etc.
This works fine if you think of mutability as an added feature rather than something that needs to be removed from the superclass. Most importantly, client code should never try to make changes to an object that’s advertised as immutable, even if the object happens to be an instance of a mutable subclass.
So, if a method returns a NSArray
, you might actually get back an instance of NSMutableArray
, but you should always treat it as immutable anyway.
2
It is legitimate, and sometimes useful, to have a subtype of a mutable type which includes additional immutable information not present in the base. It is also legitimate to have a subtype add mutable members to a type whose base members are immutable, provided that the base type makes no promises about the immutability of any members other than its own. It is generally illegitimate for subtypes of a type which promises immutability to add any visibly-mutable state, whether or not that state can be observed through a base-type reference.
The problem is that a guarantee of immutability generally implies a guarantee that persisting an object reference is equivalent to persisting the object state, and that references to the object may be freely shared as a means of sharing the current state. Such guarantees will not really hold if the object has any mutable state, whether or not such state is visible through a base class reference.
What I would suggest as an approach is to have an abstract ReadableFoo
type, with MutableFoo
and ImmutableFoo
subtypes. It may be helpful to have AsMutable
, AsNewMutable
, and AsImmutable
abstract methods in the base type so that an object which received a ReadableFoo
could use it as intended.