CSC/ECE 517 Fall 2010/ch7 7f LG
Call “super” Anti-Pattern
Introduction
Having to call super when inheriting from a super-class is considered an anti-pattern or code smell. Whenever you have to remember to do something besides inheriting the method every time, chances are that you will forget it and this is a sign of a bad API. We can solve this by defining a public method that does the housekeeping and calls a second method, often called the 'hook', which the subclass has to override. Following this refactor will strengthen the API by alleviating the programmer from remembering to call super each time.
Description
It is not the need to perform an action on the super class, but the requirement of calling the parent what we considered an anti-pattern. An API following the call super anti-pattern relies on the user to make the call, hence malfunctioning whenever the user forgets to add the call to his/her subclassed method.
Adding a 'hook' is not always possible; the source code of the parent class might not be available in a closed/black-box API. This is not the only problem inheriting closed API classes; the user must keep an eye on the changes done in the class by the developer because it might break the subclass. A possible solution to the black-box class is languages’ annotation capabilities.
Example
The following example is inspired in both the Wikipedia [1] and Martin Fowler’s [2] examples. By design, each subclass of vehicle should call super when overriding MoveForward and then perform their specific behavior. The programmer must remember to do so, if he does not, the vehicle will not move.
public class Vehicle { ... public void MoveForward() { // Housekeeping ValidateMove(); UpdateDisplay(POSITION); ... } ... } public class Car : Vehicle { ... public void MoveForward() { base.MoveForward(); UpdateDisplay(WHEEL_MARKS); // Do Car-specific actions } ... }
Following the solution proposed in Martin Fowler’s article [2], we refactor this code adding a 'hook' called AfterMoveForward, which is the method the subclasses must override. Whenever we call MoveForward within a Car object, the appropriate movement and car-specific action will be performed.
public class Vehicle { ... public void MoveForward() { // Housekeeping ValidateMove(); UpdateDisplay(POSITION); ... AfterMoveForward(); } protected abstract void AfterMoveForward(); ... } public class Car : Vehicle { ... public void AfterMoveForward() { UpdateDisplay(WHEEL_MARKS); // Do Car-specific actions } ... }
Real World Examples
Ruby on Rails
Refactoring Rails 1.x tests to work with Rails 2.x might require the use of the call super anti-pattern. Michael Koziarski points out that functional tests inheriting ActiveSupport::TestCase that define a custom setup method must call super in Rails 2.x [3]. A custom setup is one that does something else besides defining @controller, @request and @response.
class xControllerTest < ActionController::TestCase def setup super ... end ... end
References
[1] Wikipedia. (2010, December) Wikipedia - Call super. [Online]. http://en.wikipedia.org/wiki/Call_super
[2] Martin Fowler. (2010, December) Martin Fowler. [Online]. http://martinfowler.com/bliki/CallSuper.html
[3] Michael Koziarski. (2007, December) redemption in a blog. [Online]. http://blog.codefront.net/2007/12/27/dry-in-your-functional-and-actionmailer-tests-rails-20-a-feature-a-day-7/