22. Template Method
The Template Method pattern defines the skeleton of an algorithm in a base class, deferring some steps to subclasses. It lets subclasses redefine certain steps without changing the algorithm’s structure. The base class controls the flow; subclasses fill in the details.
Intent and Motivation
Intent: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
Motivation: Building a house follows a fixed sequence: lay foundation → build walls → install roof. A wooden house and a glass house share the sequence but differ in materials. Template Method encodes the invariant steps in a base class buildHouse() and declares abstract buildWalls() and buildRoof() for subclasses. The “Hollywood Principle” applies: “Don’t call us, we’ll call you” — the base class calls subclass hooks, not the other way around.
This pattern is ubiquitous in frameworks that define lifecycle hooks: test runners, servlet processing, and data pipeline ETL.
Structure (UML-like)
┌──────────────────────┐
│ AbstractClass │
├──────────────────────┤
│ + templateMethod() │ (final — defines algorithm skeleton)
│ # primitiveOp1() │ (abstract — subclass implements)
│ # primitiveOp2() │ (abstract — subclass implements)
│ # hook() │ (optional override — default empty)
└──────────▲───────────┘
│ extends
┌──────────┴───────────┐
│ ConcreteClass │
├──────────────────────┤
│ # primitiveOp1() │ (specific implementation)
│ # primitiveOp2() │ (specific implementation)
└──────────────────────┘
Participants:
- AbstractClass — defines
templateMethod()calling primitive operations and hooks. - ConcreteClass — implements primitive operations for specific behavior.
- Client — uses the ConcreteClass through the AbstractClass interface.
Java Example
abstract class DataMiner {
// Template method — defines the algorithm skeleton
public final void mine(String path) {
String raw = openFile(path);
String data = extractData(raw);
if (shouldAnalyze()) {
String analysis = analyze(data);
sendReport(analysis);
}
closeFile();
}
protected abstract String openFile(String path);
protected abstract String extractData(String raw);
protected abstract void closeFile();
protected String analyze(String data) {
return "Default analysis of " + data.length() + " chars";
}
protected boolean shouldAnalyze() { return true; } // hook
protected void sendReport(String report) {
System.out.println("Report: " + report);
}
}
class PDFMiner extends DataMiner {
protected String openFile(String path) {
System.out.println("Opening PDF: " + path);
return "pdf-raw-content";
}
protected String extractData(String raw) {
return "Extracted text from PDF";
}
protected void closeFile() { System.out.println("Closing PDF"); }
}
class CSVMiner extends DataMiner {
protected String openFile(String path) {
System.out.println("Opening CSV: " + path);
return "col1,col2,col3";
}
protected String extractData(String raw) {
return "Parsed " + raw.split(",").length + " columns";
}
protected void closeFile() { System.out.println("Closing CSV"); }
@Override
protected boolean shouldAnalyze() { return false; } // skip analysis
}
// Usage
new PDFMiner().mine("report.pdf");
new CSVMiner().mine("data.csv");
JavaScript Example
class CoffeeMaker {
// Template method
make() {
this.boilWater();
this.brew();
this.pourInCup();
if (this.customerWantsCondiments()) {
this.addCondiments();
}
console.log('Coffee ready!');
}
boilWater() { console.log('Boiling water'); }
pourInCup() { console.log('Pouring into cup'); }
customerWantsCondiments() { return true; } // hook
// Primitive operations — subclasses must implement
brew() { throw new Error('Implement brew()'); }
addCondiments() { throw new Error('Implement addCondiments()'); }
}
class EspressoMaker extends CoffeeMaker {
brew() { console.log('Brewing espresso'); }
addCondiments() { console.log('Adding sugar'); }
}
class LatteMaker extends CoffeeMaker {
brew() { console.log('Brewing strong espresso'); }
addCondiments() { console.log('Adding steamed milk'); }
customerWantsCondiments() { return false; } // hook override
}
new EspressoMaker().make();
new LatteMaker().make();
Real-World Use Cases
| Framework / System | Usage |
|---|---|
JUnit @BeforeEach / @Test / @AfterEach |
Test runner template: setup → run test → teardown. |
Spring JdbcTemplate |
Fixed JDBC flow: get connection → execute → handle results → release. |
Servlet service() method |
Template dispatches to doGet() / doPost() based on HTTP method. |
| Hibernate lifecycle callbacks | @PrePersist, @PostLoad — hooks in the entity lifecycle template. |
| React class components | componentDidMount → render → componentWillUnmount lifecycle template. |
| Build tools (Make, Gradle) | Fixed phases: compile → test → package → deploy with customizable tasks. |
Pros and Cons
| Pros | Cons |
|---|---|
| Reuses common algorithm structure across subclasses | Inheritance-based — less flexible than composition (Strategy) |
| Subclasses focus only on variant steps | Subclass proliferation for many algorithm variations |
| Controls extension points via abstract methods and hooks | final template method can frustrate frameworks needing full override |
| Hollywood Principle — base class drives the flow | Deep inheritance hierarchies can be hard to understand |
| Inversion of control — framework calls your code | Hard to change algorithm structure without modifying base class |
When to Use vs When NOT to Use
Use when:
- Multiple classes share the same algorithm structure but differ in specific steps.
- You want to control subclass extensions by defining invariant steps.
- Common behavior should be centralized to avoid duplication.
- You are building a framework with lifecycle hooks for user code.
Do NOT use when:
- Algorithms differ entirely in structure, not just individual steps (use Strategy).
- Only one implementation exists — a single class with all logic is simpler.
- Composition and delegation are preferred over inheritance (Strategy is more flexible).
- Runtime algorithm switching is needed (Template Method is compile-time via subclassing).
Common Mistakes
- Not marking template method
final— subclasses override the skeleton, breaking the invariant flow. - Too many abstract methods — if most steps vary, Strategy may be a better fit.
- Hooks with side effects — hook methods should be simple predicates or light overrides.
- Leaking primitive operation details — subclasses should not call each other’s primitive ops directly.
- Confusing with Strategy — Template Method uses inheritance for partial customization; Strategy uses composition for full algorithm swap.
Related Patterns
- Strategy — alternative using composition; swaps entire algorithms at runtime.
- Factory Method — Template Method often calls Factory Methods for object creation steps.
- Hook methods — empty or default methods in Template Method are the “Hollywood Principle” hooks.
- Command — can encapsulate template method steps as command objects for undo support.
- Observer — template method can notify observers at defined hook points.