Wednesday, November 26, 2008

On the Use of Template Methods

I find the template method design pattern too easy to get wrong, not trivial to maintain, and downright unnecessary--considering the alternatives--to be worth the trouble. The intent of the template method pattern as postulated by the gang of four is to "define the skeleton of an algorithm in an operation, deferring some steps to subclasses" [1]. In the Applicability section, the authors include a couple more scenarios when the pattern "should", be used:
  • "when common behavior among subclasses should be factored and localized in a common class" [1]

  • when implementing "hook operations": methods that get called at certain points in the execution of an algorithm that children may override.

I am yet to see a convincing case when the template method is the best solution for any object-oriented design problem. Here's why:

Easy To Get Wrong and Suboptimal Even When Implemented Properly

  1. When using template methods as concrete steps in the skeleton of an algorithm, the parent class--the one containing the operation skeleton--must be explicit in what step methods children must override and what methods/steps children may override. A way of communicating this in Java is having must-override methods be abstract (or pure virtual in C++) while having may-override methods have an non-final implementation in the parent class. Parents' providing default implementations, however minimal or convenience no-ops, to methods child classes must override anyway is unnecessary and confusing to readers and clients of the code.

  2. The concrete implementation of template methods in the concrete class must be final. Otherwise, allowing further inheritance of a concrete class that provides template method implementations, creates a maintenance mess as the only way to ever extends behavior is through further inheritance.


Putting 1 and 2 together we get the following:


public class FancyReportSender {
public void doSend() {
try {
// Do fancy work
Connection c = openConnection();
if (isConnectionOpen()) {
// Do more fancy work
// Send data using connection c
}
} finally {
closeConnection();
}
}
abstract protected Connection openConnection();
abstract protected boolean isConnectionOpen();
abstract protected void closeConnection();
}

public final class SerialCommFancyReportSender extends FancyReportSender {
protected Connection openConnection() {
// open and return a serial port connection
}
protected boolean isConnectionOpen() {
// return if connection was open fine ...
}
protected void closeConnection() {
// close it ...
}
}
Code Listing 1: Standard template method implementation

Some immediate problems with the approach above is that we have coupled the sender with the serial connection piece in a IS-A relationship. Besides just the standard composition-wins-over-inheritance argument, we see that we have coupled the responsibility of doing the sending with the responsibility of opening a serial type connection in the SerialCommFancyReportSender class. As a result, should we need to open a serial connection in a context other than the FancyReportSender, our SerialCommFancyReportSender is not of much use.

Furthermore, subclassing is the only way to provide these concrete connection-management steps: SerialCommFancyReportSender must extend the FancyReportSender class--as opposed to, say, implementing an interface--and this fact further restricts the class that might provide that concrete behavior.

And lastly, in practice, the line between what must and what may be overriden in a child class using template methods can get blurry over time. When common functionality exists for a given step, future developers maintaining the code might be tempted to push that commonality to the common base and expect client code to call super. Things can get sloppy very quick.

Consider the following composition alternative to template methods:

public class FancyReportSender {
private final IConnectionProvider m_connectionProvider;
public FancyReportSender (IConnectionProvider connectionProvider) {
m_connectionProvider = connectionProvider;
}
public void doSend() {
try {
Connection c = m_connectionProvider.openConnection();
if (m_connectionProvider.isConnectionOpen()) {
// do stuff with connection c
}
} finally {
m_connectionProvider.closeConnection();
}
}
}

public final class SerialConnectionProvider implements IConnectionProvider {
public Connection openConnection() {
// open and return connection on a serial port
}
public boolean isConnectionOpen() {
// return if connection was open fine ...
}
public void closeConnection() {
// close it ...
}
}

public interface IConnectionProvider {
Connection openConnection();
boolean isConnectionOpen();
void closeConnection();
}
Code Listing 2: Composition Alternative to Template Methods

This composition alternative addresses the problems above and leaves greater flexibility for clients providing the concrete steps.

Not Trivial To Maintain
Since template methods set the tone of class extension as the way of extending class behavior, in practice one often sees the application of template methods deteriorate to something like the following:

Diagram 1: Template Methods Pave the Way to Vertical Design

As a result, the class hierarchy can get very vertical, very fast. Concrete template method implementations often end up on a various "floors" in the hierarchy, and, at coding time, inspecting a piece of code in CommConnectionFancySender, say, presents a challenge to determine on what floor a method implementation that will get called at run-time, really lives.

In such a design, class attributes end up spread all over the hierarchy, and there is no protection against a future developer maintaining the code and reducing data visibility of parent data from private to protected in order to make it accessible to children.

While debugging, what we see is that stepping-into a method can cause the program counter to jump up and down the hierarchy, binding to the right method: at any given instant, for a class in such a design, a method call can "land" in a parent, a child or in the class itself.

For a project of a reasonable size, we end up with a maintenance gem on our hands.

Template Methods as Hook Operations

If we recall the GOF definition of hook operations, these are operations that get called by the parent at times when they wish to give control to their children classes. Therefore, by their very nature hook operations promote temporal cohesion, as clients often end up sticking code in the hook implementation that is there mostly because of when it needs to get called: code that ends up in concrete versions of the various Eclipse RCP advisors, such as WorkbenchWindowAdvisor.preWindowRestore() are a good case in point. Plus, when implementing hook methods as template methods, we design in the unnatural restriction that hooking in can happen only by class extension.

As an alternative to hook methods, a plain callback interface can be used: at the very least, it won't require class extending to insert an hook. But a callback interface will still keep the temporal cohesion problem.

Some variation of the observer-subject pattern is probably a better fit: it will allow orthogonal operations to register separate concrete observers, and get notified separately, thereby avoiding the temporal cohesion in a single method.

Final Thought

Template methods are not evil. But I, for one, have not come across a case when they were ever the best design decision.

3 comments:

rogerv said...
This comment has been removed by the author.
rogerv said...

The Jave enum approach to this pattern changes everything. Turns it into a completely OOP polymorphic approach that is concise, elegant, and better to maintain than alternatives:


Java enum technique - template method pattern

Stoyan Vassilev said...

Roger,

Thanks for the comment. The link referenced in the document: http://www.ajaxonomy.com/2007/java/making-the-most-of-java-50-enum-tricks .

A great deal of my post is N/A if we're dealing with enums: they can't be extended from client code, and the small finite set of enum values is closely managed inside the enum definition.

A purist might argue that in the given example, client code calls execute() directly: execute() is abstract in the enum definition, and concrete in the children: this is polymorphism, we don't have the full pattern going.

The previous argument aside, we can rearrange it a bit in order to have a t.m.--e.g. if the top-level enum provides a concrete execute() that calls does stuff and then calls abstract step1() and step2(), both of which are concrete in the concrete enum types. This is surely the best approach in this case and, in fact, it is the *only* design alternative I can think of in this case.

That said, this example is rather limited to the Java implementation of enums. It won't generalize to plain classes and C++/Pascal enums, in which case my argument holds all the same.