Disciplined Agile

The Template Method Pattern

Contents


The Template Method Pattern


Template Method Pattern

Scott Bain discusses the essential features of the Template Method Pattern. Scott is a senior Disciplined Agile technical trainer and thought leader. This is part of a series of recorded presentations brought to you by PMI Disciplined Agile.

Contextual Forces


Motivation

Abstract out the skeletal steps of an algorithm, allowing subclasses to override the specific implementations of each step.

Encapsulation

  • The steps of the algorithm.
  • The implementation of each step.
  • The variation of the implementations (how many, and the fact that they are varying).

Procedural Analog

When a series of steps varies, and they all transition from one variation to another at the same time, as a set, then one can accomplish this procedurally with a series of logical branches or switch/case statements, where all are based on the state of a common value:

int var = //state set here

//Step one
switch var {
     case 1:
     // first step, variation 1

     case 2:
     // first step, variation 2

     case 3:
     // first step, variation 3
}

//Step two
switch var {
     case 1:
     // second step, variation 1

     case 2:
     // second step, variation 2

     case 3:
     // second step, variation 3
}

//Step three
switch var {
     case 1:
     // third step, variation 1

     case 2:
     // third step, variation 2

     case 3:
     // third step, variation 3
}

Non-Software Analog

Recipes are sometimes expressed as a series of steps for preparation, combination, processing (cooking) and finishing for consumption. Sometimes, however, the same recipe can be used to feed a varying number of people. When this is the case, the amounts are sometimes omitted, to be filled in based on the number of people to be fed. In these cases, the recipe is the same, but the amounts all vary together, based on the state "number to be fed". The recipe (non-changing part) would be the template method, and the amounts would be the delegated methods that are overridden.

 

Implementation Forces


Example

Template 0 

 

public abstract class TargetAbstraction {
     public void templateMethod() {
           step1();
           step2();
           step3();
     }

     protected abstract void step1();
     protected abstract void step2();
     protected abstract void step3();
}

public class implementationA extends TargetAbstraction {
     protected void step1() {
           // Implementation of Step1, version A here
     }
     protected void step2() {
           // Implementation of Step2, version A here
     }
     protected void step3() {
           // Implementation of Step3, version A here
     }
}

public class implementationB extends TargetAbstraction {
     protected void step1() {
           // Implementation of Step1, version B here
     }
     protected void step2() {
           // Implementation of Step2, version B here
     }
     protected void step3() {
           // Implementation of Step3, version B here
     }
}

Or, to show our non-software analog (see above) in UML:

Template 1 

Questions, concerns, credibility checks

Can all the variations of this behavior be resolved to the same set of steps without making them overly generic, or without forcing any one of them to give up critical behavior?

It should be noted that the Template Method Pattern is meant to vary the steps in an algorithm, not to vary a set of algorithms that are entirely seperate resposibilities. The pattern is best used with a single algorithm that can be functionally decomposed into "steps". If these are really seperate algorithms, then this pattern would erode both cohesion and testability.

Will some subclasses require different/more parameters or returns from the delegated methods? See the "ignore parameters" paragraph in the "Options in Implementation" section.

The delegated methods, which will be overridden in the subclasses, should not be made public to avoid any possibility that other entities will become coupled to them. If the implementation language does not allow abstract methods to be protected (some languages force all abstract methods to be public), then you should implement the methods in the base class with default or stubbed-out behavior.

Options in implementation

Sometimes the variations of the specific behavior can require different parameters. When this happens, creating a generic interface for these methods can require the use of "ignore parameters". For example:

public class StarshipWeapon {
     // This is the Template Method
     public int fire(int power){
          int damage = 0;
          if(hasAmmo(power)){
               if(doesHit(range, direction)){
                    damage=calculateDamage(power);
               }
          }
          return damage;
     }
     protected abstract bool hasAmmo(int power);
     protected abstract bool doesHit(long range,double direction);
     protected abstract int calculateDamage();
}
public class PhotonTorpedos extends StarshipWeapon {
     protected bool hasAmmo(int ignorePower){
          // return true if there are torpedos remaining,
          // else return false
     }
     protected bool doesHit(long range, double direction){
          // Calculation based on range and bearing to the target, return true if hit, false if miss
     }
     protected int calculateDamage(){
          // Torpedoes always do full damage if they hit
          return 500;
     }
}
public class Phasers extends StarshipWeapon {
     protected bool hasAmmo(int Power){
          // Store power for later use in calculating damage
          // return true if there is enough energy to fire phasers at this power
     }
     protected bool doesHit(long range, double ignoreDirection){
          // Store range for later use in calculating damage
          // Phasers always hit
          // Direction is irrelevant for Phasers, so name the parameter accordingly
          return true;
     }
     protected int calculateDamage(){
          // Take power, attenuate for range, and return the
          // reduced amount as the damage to be applied
     }
}

The use of the term "ignoreXXX" as the parameter names in subclasses that do not use the parameter makes it clearer during maintenance that this is being done. This allows all subclasses to operate using the same template method.

 

Consequent Forces


Testing issues

The base class algorithm can be tested by deriving a Mock object just for testing

Template 2

Often developers ask how the individial "step" methods can be tested. Given that unit tests ideally test resposibility, not implementation (we would prefer to be able to refactor implementation while passing the same suite of tests), the general principle is that they should not be tested individually. However, as a practical matter, there are times when this is desireable, even though a step is truly part of an overall algorithm. There are various techniques, depending on language and platform, that can be used. A few examples are:

  • Making the test and the class being tested "friends" (C++)
  • Using package-private access control, and making the test part of the package
  • Testing through a testing proxy that is either a friend or part of the package
  • Making the unit test a subclass of the class being testing
  • Wrapping private methods in public delegates (.Net)
  • etc...

It can be said, however, that the very desire to test a method on its own is a strong indicator that it belongs it its own class. The step method would, therefore, remain private or protected, but then would delegate to a seperate class where the implementation would be public, and testable on its own. This is an example of how the testability of a design can inform our design decisions, and is one aspect of Test Driven Development.

Cost-Benefit (gain-loss)

The use of a Template Method pushes redundant aspects (skeletal algorithm, or template method) to the base class, leaving non-redundant aspects (variation) in derived classes. Separates what changes from what does not, improving cohesion.

  • Can create overly generic designs.
  • Implementing new subclasses is simpler, because they do not have to implement the skeletal algorithm.
  • Improves readability.