I’m interested to find out what us Cocoa developers (alright, I know my opinion already) think of the following distinction between Foundation and, well any other object-oriented foundation library.
The distinction is this. In many libraries, compound objects (not only collections, but strings which are many-character objects and data which are many-byte objects) have both immutable and mutable varieties. We’re familiar with the Cocoa pattern, where we have the base immutable class e.g. NSArray (class clusters are an irrelevant complication for now) and a mutable subclass, e.g.:
@interface NSMutableArray : NSArray
That’s not how everyone else does it. They have distinct class hierarchies for the mutable and immutable types; for instance in Java we have String and StringBuilder. The two classes aren’t related, but you can create a StringBuilder given a String and vice versa. You certainly can’t pass one in where the API expects the other. The design pattern used here is called Builder.
The argument in favour of the subclass approach is that mutable arrays are just specialised versions of arrays. You can find out how long they are, what object is at what index, and you can also do all the add/remove/replace stuff on top, so these objects are arrays with extra functionality, and thus should be subclasses of the array type.
The argument against is that this specialisation is bogus. Client code can’t treat all array objects the same in case it gets a mutable array, and therefore mutable arrays can’t be used as arrays and shouldn’t be subclasses. They violate the Liskov substitution principle: the rule that if I expect to work with one class, any of its subclasses must be usable in its stead.
We can use an example from Foundation to bolster the second argument a bit. The NSFastEnumeration protocol works on all collections, except that when implemented on a mutable collection it must employ additional checks to protect against the collection being mutated while it’s iterating over it. So we need extra client code to deal with some subclasses, and thus Liskov is violated.
What do you think? Would you have designed Foundation the same way NeXT did? Perhaps there are other changes you would have made. Let the world know in the comments box.
Would Liskov be happy if he considered that the contract that it offered by NSFastEnumeration is that it requires anything being enumerated not to change while it’s operating?
IIRC the earlier versions of NSFastEnumeration in fact didn’t check for mutation – the check was added as a debugging aid to highlight when the contract was being violated.
If you accept this, couldn’t you make the same argument of all APIs that ask for an NSArray – i.e. that the specification of NSArray – as opposed to NSMutableArray means that there’s an implicit contract that while the array is in use it should not be mutated?
Jamie, the problem with implicit contracts is that they’re hard to discover – by nature it’s easier to deal with an explicit contract because it’s stated.