Low-level designCore8 min
Template Method
Write the steps of a process once, in a base class, and leave the steps that differ to subclasses. The order is fixed in one place, and a new variation fills in only what is its own.
Two kinds of print job
CardJob and PosterJob each run the routine: check, lay out, print, finish.
1 · The situation
Two kinds of print job, one routine
A print shop runs every job the same way: check the customer's file, lay the pages out on press sheets, print, and finish. Business cards are imposed 21 to a sheet, then cut and boxed. Posters are tiled across 4 sheets each and laminated, and the customer approves a proof first, since a poster reprint is expensive.
The first version has a class per kind of job, CardJob and PosterJob, each with its own run() that walks the routine from top to bottom.
class CardJob {
private final String file;
private final int copies;
CardJob(String file, int copies) {
this.file = file;
this.copies = copies;
}
void run() {
System.out.println("Job: " + copies + " business cards");
System.out.println(" check " + file);
int sheets = (copies + 20) / 21;
System.out.println(" impose 21 cards per sheet");
System.out.println(" print " + sheets + " sheets");
System.out.println(" cut and box");
}
}
class PosterJob {
private final String file;
private final int copies;
PosterJob(String file, int copies) {
this.file = file;
this.copies = copies;
}
void run() {
System.out.println("Job: " + copies + " posters");
System.out.println(" check " + file);
System.out.println(" send a proof to the customer");
int sheets = copies * 4;
System.out.println(" tile each poster on 4 sheets");
System.out.println(" print " + sheets + " sheets");
System.out.println(" laminate");
}
}
public class Main {
public static void main(String[] args) {
new CardJob("cards-ruiz.pdf", 500).run();
new PosterJob("fair-poster.pdf", 3).run();
}
}The class diagram of this version, drawn from its code: two unrelated classes with the same shape.
One order of cards and one of posters:
Job: 500 business cards check cards-ruiz.pdf impose 21 cards per sheet print 24 sheets cut and box Job: 3 posters check fair-poster.pdf send a proof to the customer tile each poster on 4 sheets print 12 sheets laminate
2 · The change
The routine lives in every job class
Two changes arrive at once. The shop adds booklets, and the owner wants one change to the routine for every job: a check that the file's fonts are embedded. Look at where the routine is today:
class CardJob {
private final String file;
private final int copies;
CardJob(String file, int copies) {
this.file = file;
this.copies = copies;
}
void run() {
System.out.println("Job: " + copies + " business cards");
System.out.println(" check " + file);
int sheets = (copies + 20) / 21;
System.out.println(" impose 21 cards per sheet");
System.out.println(" print " + sheets + " sheets");
System.out.println(" cut and box");
}
}
class PosterJob {
private final String file;
private final int copies;
PosterJob(String file, int copies) {
this.file = file;
this.copies = copies;
}
void run() {
System.out.println("Job: " + copies + " posters");
System.out.println(" check " + file);
System.out.println(" send a proof to the customer");
int sheets = copies * 4;
System.out.println(" tile each poster on 4 sheets");
System.out.println(" print " + sheets + " sheets");
System.out.println(" laminate");
}
}Half of each run() is the same in both classes, and it's the half that defines how the shop works:
Step | Same for every job? | Today written in |
|---|---|---|
Announce the job | Yes | Every class |
Check the file | Yes | Every class |
Send a proof | Only for some jobs | The classes that need it |
Lay out the sheets | No | Each class |
Yes | Every class | |
Finish | No | Each class |
A booklet class would be a third copy of the routine. The font check is a change to every copy, in the same place in each, and the copies can drift: a class that checks fonts after printing compiles and runs just as well.
3 · The refactor
Write the routine once, and leave gaps
Start from the first version. Move the routine into a base class, and turn each step that differs into a method the subclasses supply:
PrintJob.run() holds the fixed order: announce, check, proof if needed, lay out, print, finish. It's final, so a subclass can't reorder it. The steps it calls, product(), layout(), sheets() and finishing(), are abstract.
abstract class PrintJob {
protected final String file;
protected final int copies;
PrintJob(String file, int copies) {
this.file = file;
this.copies = copies;
}
final void run() {
System.out.println("Job: " + copies + " " + product());
System.out.println(" check " + file);
if (needsProof()) System.out.println(" send a proof to the customer");
System.out.println(" " + layout());
System.out.println(" print " + sheets() + " sheets");
System.out.println(" " + finishing());
}
protected boolean needsProof() {
return false;
}
protected abstract String product();
protected abstract String layout();
protected abstract int sheets();
protected abstract String finishing();
}After the second step the program prints exactly what it printed before. After the third, booklets run through the same routine:
Job: 500 business cards check cards-ruiz.pdf impose 21 cards per sheet print 24 sheets cut and box Job: 3 posters check fair-poster.pdf send a proof to the customer tile each poster on 4 sheets print 12 sheets laminate Job: 50 booklets check garden-zine.pdf send a proof to the customer fold 16 pages onto 4 sheets print 200 sheets fold and staple
Copies of the routine with three kinds of job. The font check now goes into one method, PrintJob.run().
4 · The pattern
Template Method: a fixed skeleton with steps to fill
Template Method defines the skeleton of a procedure in a base class method, the template method, and leaves some of its steps to subclasses. The base class decides the order and what happens between steps; subclasses decide what each step does. Steps that subclasses must supply are abstract; steps they may change are hooks, with a default.
The roles in the print shop's code: PrintJob is the abstract class with the template method, and each job a concrete class.
Role | In the print shop | Its job |
|---|---|---|
Abstract class |
| Holds the template method and declares the steps |
Template method |
| Fixes the order of the steps; final |
Abstract steps |
| Must be supplied by every subclass |
Hook |
| Has a default; subclasses override it only when they need to |
Concrete class |
| Supplies the steps for one kind of job |
The inherited run() calls down into the subclass for each step it leaves open.
The base class calls the subclass, not the other way around: subclasses never call run()'s steps themselves or decide the order. That reversal is what keeps the routine in one place.
5 · What it costs
Inheritance, with its ties
- It's inheritance. Subclasses depend on the base class's protected methods and on when the template calls them, the fragility described in Inheritance. A change to the template can break every subclass.
- One skeleton per class. A job can't take its layout from one variation and its finishing from another; each combination is a subclass.
- The order is fixed for everyone. That's the point, until one job genuinely needs a different order; then the template grows a hook, or the job doesn't fit.
Situation | Template Method or something else? |
|---|---|
Several classes run the same procedure with different steps | Template Method |
Steps should be mixed and matched at run time | Strategy: hold each varying step as an object |
Only one step varies | A Strategy, or a function passed in, is lighter |
The procedure itself differs between variations | Neither: they are different procedures |
6 · Getting it right
Lock the order, open only the steps
- Make the template method impossible to override. In Java mark it
final. Otherwise a subclass can replace the whole routine, and the shop's checks with it. - Keep the steps out of reach of callers. Steps are
protected: subclasses supply them, but only the template calls them. - Few abstract steps, sensible hooks. Every abstract step is work for every subclass. Give optional steps a default, as
needsProof()returns false. - Keep the steps pure where you can. Here the steps return text and numbers and
run()prints, so each step is easy to test and the output format lives in one place.
Language | Fixing the order |
|---|---|
Java | A |
C++ | A public non-virtual template calling protected virtual steps: the non-virtual interface idiom |
JavaScript | No |
Python |
|
7 · Look-alikes
Fixed steps, varied by inheritance or by objects
Template Method shares its goal, varying part of an algorithm, with other patterns. What differs is how the varying part is supplied:
Pattern | The varying part | Chosen |
|---|---|---|
Template Method | Steps of a fixed procedure, supplied by subclasses | When the class is written |
Strategy | A whole job, supplied by an object the context holds | At run time, and can be swapped |
Factory Method | Which object to create, supplied by a subclass | When the class is written |
Hooks and callbacks | A function the framework calls at set points | At run time, by registering it |
A factory method is often one step of a template method: the template does the common work and calls createSomething() where a subclass decides what to make. Template Method and Strategy are the two classic ways to vary part of an algorithm: by inheritance or by composition.
8 · In practice
Templates in the standard library
Java's AbstractList is a template: to make an unmodifiable list, a class extends it and supplies get(int) and size(), and every other List method, iteration, searching, comparison, is built on those two. To make a modifiable list it also overrides set(), and for a resizable one add() and remove().
The practice checks recognize a Template Method when an abstract class has a concrete method that calls at least two of its own steps, one of them abstract, and subclasses supply them. A template that is final counts in its favor, and a subclass that overrides the template itself is flagged.
Check yourself
4 questions
Takeaways
Remember this
- Template Method writes a procedure once in a base class and leaves the varying steps to subclasses.
- The base class calls the subclass's steps, never the other way around.
- Abstract steps must be supplied; hooks have defaults subclasses may override.
- Make the template method final so the order can't be changed.
- It varies behavior by inheritance; Strategy does it by composition.