CallSuper

bad things · language feature

tags:

Call Super is a minor smell (or anti-pattern if you like) that crops up from time to time in OO frameworks. Its symptoms are pretty easy to spot. You are inheriting from a super-class in order to plug into some framework. The documentation says something like "to do your own thing, just subclass the process method. However it's important to remember to start your method with a call to the super-class". An example might be something like this.

public class EventHandler ...
  public void handle (BankingEvent e) {
    housekeeping(e);
  }
public class TransferEventHandler extends EventHandler...
  public void handle(BankingEvent e) {
    super.handle(e);
    initiateTransfer(e);
  }
    

Whenever you have to remember to do something every time, that's a sign of a bad API. Instead the API should remember the housekeeping call for you. The usual way to do this is to make the handle method a Template Method, like this.

public class EventHandler ...
  public void handle (BankingEvent e) {
    housekeeping(e);
    doHandle(e);
  }
  protected void doHandle(BankingEvent e) {
  }
public class TransferEventHandler extends EventHandler ...
  protected void doHandle(BankingEvent e) {
    initiateTransfer(e);
  }
    

Here the super-class defines the public method and provides a separate method (often referred to as a hook method) for the subclass to override. The subclass writer now doesn't have to worry his ugly head about calls to super. Furthermore the super-class writer is free to add calls after the subclass method if she wishes.

There are a couple of ways to define the hook method. In this case I've shown an empty implementation. This is useful if many subclasses don't need to provide their own additional behavior. If many subclasses have to do the same thing, you can consider a default implementation, which can also be a template method itself to allow for variation within the common scheme. If every subclass should provide unique behavior, then you can make the hook method abstract in the super-class.

One of the problems with this is that there's usually no way to indicate that a certain method is a hook method, i.e. that it's the one that the framework writer expects to be overridden. You do see conventions (the handle/doHandle is one common one), but mostly you have to explain how it works. One of the best ways to explain it is with an example. When I'm looking at one of these cases I usually find the best thing to do is look at an existing subclass and see what it does.

In this case there's single, and relatively obvious hook method. Often, however, there are many methods, all of which could be hooks, depending on how much control the subclass wants to take. A quick glance at the abstract base class for my HTML layout class shows half a dozen methods that could be overridden. Some subclasses want to do something simple, and override small hooks; others need to do more fancy things and override larger scoped methods.

In some languages you can Seal the handle method to prevent the subclass from overriding it. Since I have an EnablingAttitude, I'm usually reluctant to do this - especially if the inheritance is effectively a published interface. The argument in favor of sealing is that it means that subclasses can't break your super-class. I don't think of overriding the wrong thing as 'breaking the super-class'. The subclass and super-class have to collaborate closely, after all inheritance is a very intimate relationship. Either the whole thing works or it doesn't. Sealing can be a good way to indicate to someone that they are overriding the wrong thing, and as such I'd be inclined to use it for a non-published interface (i.e. the subclasses are part of the same code base). My problem with sealing is that a subclass may want to override the handle call to do something particularly fancy. I can't predict the subclass's needs, so I'd rather say "go ahead, but on your own head be it", rather than "no". If it's all one code base then we can always take the seal off if we need to.


Multi-Level Hooks

So I hope you can see that you shouldn't have to rely on call super for this kind of situation. But there's a complication when you have more than one level of framework.

Here I'll switch to a real example, one that has come up from time to time: JUnit. JUnit uses a template method to control the overall running of its test cases, it looks like this:

public abstract class TestCase
  public void runBare() throws Throwable {
    setUp();
    try {
      runTest();
    }
    finally {
      tearDown();
    }
  }
  protected void setUp() throws Exception {
  }
  protected void tearDown() throws Exception {
  }

It's a familiar template method with two empty hook methods for overriding. So far, so good; it's easy to add your own set up and tear down code as you need it.

The complication comes when a user wants to derive another framework from JUnit. There are plenty of examples out there, but let's take some simple case where we have a project specific convention, say something like this.

public class AlphaTestCase extends TestCase
   protected void setUp() throws Exception {
     alphaProjectSetup();
   }

This is where you run into the call super problem. So if we use the advice from earlier on we could redefine it like this.

public class AlphaTestCase  extends TestCase...
  final protected void setUp() throws Exception {
    alphaProjectSetup();
    doSetUp();
  }    
  protected void doSetUp() throws Exception {        
  }

While this works, it runs into the problem of confusing anyone who is familiar JUnit. Every project they've been on, every book they've read says they should override setUp, not this new-fangled doSetUp. This is a good case to use final, since there's such a high chance that people will get confused. But even with final, the confusion a different setup method causes is very painful.

There is another option. The second level framework can override the caller of setUp.

public class AlphaTestCase extends TestCase...
  public void runBare() throws Throwable {
    alphaProjectSetup();
    setUp();
    try {
      runTest();
    }
    finally {
      tearDown();
    }
  }
  protected void setUp() throws Exception {
  } 

Now everyone can use setUp just like normal. The framework writer also have more options if they want to add other bits of behavior in interesting places. This is always an option worth considering - if a template method doesn't work where you are, consider going up a level.

Of course there's no such thing as a free template. In some cases you can't do this because you're not sure what to do because the source isn't available. In others the framework writers have a DirectingAttitude and have sealed the calling method, stopping you from overriding it.

Even if you can do it, there is something to watch. What if the primary framework writers need to change the method you've overridden (which indeed JUnit did on the 9th of October 2004). As usual the power of inheritance comes with responsibility, you have to keep an eye on what changes occur in the super-class.

Another option that's coming available now is to use an Annotation. JUnit and other Java based frameworks have been working with annotations in the last few months, following the lead set by NUnit. Annotations allow you to give more metadata to a method than just the name, allowing you more options in this kind of situation. But that's the topic for another day.