Understanding self and super
TL;DR
self
and super
both represent the reciever of a message. The difference is in the method lookup. A self send is always dynamically resolved while a super send is static.
Introduction
Although all single-inheritance OO languages support some form of self
and super
mechanisms, they are often not well-understood, so it is worthwhile to take a closer look at them both.
The key point to understand is that both self
and super
represent the receiver (the object itself), but self
resolves message sends
dynamically
, while super
sends are purly
static
.
Understanding self
Self sends are useful not only for breaking a complex method into helper methods, but also to enable the creation of generic code that can be extended by subclasses.
Consider Collection>>#isEmpty
.
The #isEmpty
method is implemented generically in the abstract class Collection
, as the #size
method can be implemented in different ways by its subclasses.
This works because the self send is
dynamic
— only at run time will it be determined which size
method will be performed, since it will depend on the run-time class of the receiver.
Understanding super
Super sends, on the other hand, allow you to extend a method in a subclass, by composing it with the behavior of that same method defined in the superclass.
Consider, for example, SortedCollection>>#sort:
.
SortedCollection
overrides the #sort:
method it inherits from OrderedCollection
, without changing the core logic, but additionally saving the sortBlock
in a private slot. Since it overrides #sort:
, the only way to avoid duplicating its code is to invoke it using a super send.
So much is clear. What is perhaps less obvious is that the super send is purely
static
. The sort:
method to perform is statically known when this code is compiled. It will
always
refer to OrderedCollection>>#sort:
, even in any subclasses that might be defined later.
Using both self and super
We can see the difference clearly in the implementation of Symbol>>#=
.
A symbol equals another object either (i) if they are identical (self == ...
), or (ii) if they are of the same class (self class == ...
),
and
they have the same values as Strings.
While the self sends are dynamic, the super send refers statically to the method String>>#=
inherited from the superclass, String
.
Never use super to perform a different method
Super sends are there only to extend an inherited method, and provide a way to access the overridden implementation. They should not be used to hardwire other methods. Instead a self send should always be used instead.
The reason is clear — by statically hardwiring the method to be performed, you prevent subclasses from plugging in a different implementation. Whether the helper method is implemented in the class or not, it should be performed by a self send.
Caveat. Of course there are exceptions to this rule, but they tend to be unusual and even questionable.
Consider, for example, SortedCollection>>#add:
.
Why is this a super send? Well, it turns out that SortedCollection
removes
the inherited method for SortedCollection>>#insert:before:
because that method would conflict with its responsibility to maintain the order of its elements.
Of course that begs the question whether SortedCollection
should inherit from OrderedCollection
in the first place, if it does not fulfil the same responsibilities. Perhaps the hierarchy should be refactored to avoid the ugly removal of inherited methods.
Here's another curious example: Collection>>#printNameOn:
Why isn't this a self send? Well, it's because Collection
introduces a more intelligent #printOn:
method, but the old method should still be available under a new name: Collection>>#printOn:
So, as a general rule, you should only use a super send to extend the same method, but there may be some curious situations that justify breaking this rule.
A puzzle
What is the result of evaluating this code?
self == super
Is there any situation in which you might expect a different result?