Re: Call-Super antipattern
Philipp wrote:
Daniel Pitts wrote:
Philipp wrote:
Hello,
I just read about the "Call-Super" antipattern
(http://en.wikipedia.org/wiki/Call_super) and I'm not completely
convinced about the "anti-pattern" property of this construct.
Suppose you have a class hierarchy which makes sense, something like:
Car extends Vehicle
Ford extends Car
and they all have a start method:
Why is it bad for each start to call the super method? (in case each
one adds the super's behavior)
In particular, a proposed solution in the wiki article is to use the
template method pattern. How does that improve the design? In a
multi-inheritance structure as this example, you will still have to
choose between replicating code (Copy-Paste antipattern) or adding
template methods (hooks) for all possible subclasses.
Thanks for your comments.
Philipp
public class Vehicle {
public void start(){
// unlockDoor();
}
public static class Car extends Vehicle {
@Override
public void start() {
super.start();
// startEngine();
}
}
public static class Ford extends Car {
@Override
public void start() {
super.start();
//switchRadioOn();
}
}
}
<rant>
First, The "Ford is-a Car" is an example of a terrible design. A Car
has-a Make and has-a Model, Ford is a Make, FordModelT is a Model.
Think about it, would you want to maintain the class hierarchy that
contains RedHondaCivicWithSpoiler.
</rant>
Obviously. But making up good examples is an art of its own...
Despite that, a framework should rely as little as possible on
consumers to "do the right thing". The right way to do it is to have
an protected overridable method that isn't expected to call super, and
the non-overridable public method that delegates to the appropriate.
In other words, the work-flow should be defined by the class higher in
the hierarchy, and the details by the lower.
While I understand and agree with the above, this does not exactly solve
my problem (or it is not clear to me how). The point is, that all
classes in the hierarchy are instantiable and all should be startable
using the start() method.
Is there a "good" reason its a hierarchy? Can you refactor to use
Composition instead of Inheritance? If you can limit to one base class,
and THEN use the start/hook approach (not the best names, IMO, but thats
another thread ^_^). In this approach, start() would be final, and call
hook() on itself, and start() on all its composed objects (if you have a
Collection, this is a simple for-each).
So making start() call a hook() method in the top class, which can be
overriden is only solving the problem for the first child level. For the
grand-child, the question remains: should grandChild.hook() call
child.hook() or should we make child.hook() call anotherHook(), which
can then be overriden by the grand-child class. Martin Fowler goes to
great length about this in
http://www.martinfowler.com/bliki/CallSuper.html but in the end, he
comes up with two possible solutions which are IMHO, less elegant than
calling super (as if that was totally forbidden).
The reason its dangerous is that it couples your base class and derived
classes more than they should be in a flexible design.
The two solutions are:
- reimplement the hook-calling method (ie. start() ) in lower classes.
This is ugly IMHO as you need to know a lot about how the parent works
(and also copies code). Also you have to adapt the code in child
classes, if the parent class changes.
- Use a new hook method (as explained above) in each subsequent level of
class hierarchy. This makes the API at least as brittle because it is
not clear from the code point of view which method should be overriden.
Having doc saying "for this level, override stillAnotherHook() and leave
start(), hook() and anotherHook() alone" is not much better than having
doc saying "when overriding start(), call its super implementation"
That's what creating final methods are for, they can't be accidentally
overridden.
Maybe I'm missing the point. And I totally agree that inversion of
control or template method are appropriate for frameworks (although the
example given by Martin Fowler is precisely about the JUnit framework).
I think the main point is to try to keep as much to possible to having
one class have only one responsibility, if you're class has to take care
of there geriatric parent, they aren't going to have as much time for
themselves :-). IOW, it makes it more "heavy" psychologically to
realize that you have to call "super" or risk breaking the flow.
Sometimes it makes sense to call super, but when most of your whole
hierarchy does it, that indicates an opportunity to at least *consider*
a different design pattern.
It may be tricky to find the other pattern that works, from what design
you already have. Nobody says big refactoring is easy. It is more than
worth it when you realize how much easier it is to extend the new design.
--
Daniel Pitts' Tech Blog: <http://virtualinfinity.net/wordpress/>