4

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
  • 1
    Noteworthy: "Immutable superclass with mutable subclass" is exactly how Collections in Kotlin work.
    – julaine
    Commented Dec 5, 2023 at 10:37

2 Answers 2

7

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
  • Thank you for this, but just to clarify a little. My Model does essentially assume that the children are immutable, as it uses them in sets, hashmaps etc. My work around is to not override the equals/hashcode methods; so that the 'immutable parts' are the only parts used for equal and hash. Is this still something you would call acceptable? I'm maintaining the 'important part' of the immutability assumed below essentially.
    – dsollen
    Commented May 10, 2013 at 18:56
  • 1
    If your model assumes that the objects are immutable, then there's really no problem -- it's not going to try to change them. What I was trying to get across above is that you want to avoid having those objects change when the model (or anything else) doesn't expect them to. So, if some method in your model advertises that it returns an immutable object, clients of the model shouldn't test the objects to see if maybe they're really mutable after all, and change them if they are. I can't speak to your overrides -- it really just comes down to when you want to consider two objects equal.
    – Caleb
    Commented May 10, 2013 at 19:19
1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.