A subclass understands the same messages as its superclass since every method defined by the superclass is inherited by the subclass if it doesn't provide its own method definition for the particular message. In the case of Counter and TwoCounter this means that any place where an object is handled as if it is a Counter instance, a TwoCounter instance can be substituted.
For example, the following method retrieves the nextValue from a Counter object passed as the argument:
int getNextValueFrom Counter counter { = [counter nextValue]; } |
When this method is passed a TwoCounter object instead of the expected Counter, sending the nextValue method is still valid. In fact, sending any message understood by a Counter will be valid, since the TwoCounter is a subclass of the expected Counter class. This observation is universally applicable: any time a certain class is expected, passing a subclass is equally valid.
Now, what happens when the following code is executed?
Counter c2 = [TwoCounter alloc]; int i = [c2 nextValue]; |
The variable c2 has type Counter, but actually references a TwoCounter object. When the nextValue message is sent, which method is actually invoked: the one defined by Counter, because that is the type of c2, or the one defined by TwoCounter, because that is the actual class of the receiver? The answers is that the actual class of the receiver, and not the caller's idea of the receiver's type, decides which method is invoked. This is called polymorphism: the method invoked depends only on the receiver.
When looking back you'll notice that at a lot of places in the preceding sections, the polymorphism was already used, without mentioning it. Yet, intuitively, the meaning was always obvious.