Template Method
Define the skeleton of an algorithm in a base class and let subclasses override specific steps without changing the algorithm's structure. The invariant part lives in one place; the variant parts are deferred to subclasses.
The Problem
You’re building a data mining tool. You need to process Excel files, CSV files, and PDFs. The overall pipeline is identical for all three: open file → parse → extract raw data → analyse data → send report. But the “open file” and “parse” steps differ per format.
Without Template Method you’d either duplicate the analysis/reporting code in each class, or use a complex conditional inside one class. Either way, the shared skeleton gets scattered or buried.
The Solution
Put the algorithm skeleton in an abstract base class as a non-overridable template method. Mark the steps that vary as abstract (or provide default implementations for optional hooks). Subclasses override only the variant steps.
Real-World Analogy
A house construction blueprint. Every house follows the same sequence: lay foundation → build walls → build roof → install plumbing → install electricity. The foundation and walls can be concrete or wood — the materials (variant steps) differ. But you never install electricity before the walls — the sequence (template) is fixed.
TypeScript Example
// Abstract class defines the algorithm skeleton
abstract class DataMiner {
// Template method — sealed: defines the fixed sequence
mine(filePath: string): void {
const raw = this.openFile(filePath);
const data = this.extractData(raw);
const parsed = this.parseData(data);
const results = this.analyseData(parsed);
this.beforeReport(); // hook — optional
this.sendReport(results);
}
// Abstract steps — subclasses must implement these
protected abstract openFile(path: string): string;
protected abstract extractData(raw: string): string[];
protected abstract parseData(data: string[]): Record<string, number>[];
// Concrete steps — shared by all subclasses
protected analyseData(data: Record<string, number>[]): string {
const totals: Record<string, number> = {};
for (const row of data) {
for (const [key, val] of Object.entries(row)) {
totals[key] = (totals[key] ?? 0) + val;
}
}
return JSON.stringify(totals, null, 2);
}
protected sendReport(results: string): void {
console.log('[Report] Sending analysis results:');
console.log(results);
}
// Hook — optional override, no-op by default
protected beforeReport(): void {}
}
// Concrete miner for CSV
class CSVMiner extends DataMiner {
protected openFile(path: string): string {
console.log(`[CSV] Opening ${path}`);
// Simulate file content
return `name,sales,returns\nAlice,100,5\nBob,200,10\nCarol,150,3`;
}
protected extractData(raw: string): string[] {
return raw.split('\n');
}
protected parseData(data: string[]): Record<string, number>[] {
const [header, ...rows] = data;
const keys = header.split(',');
return rows.map(row => {
const values = row.split(',');
return Object.fromEntries(
keys.map((k, i) => [k, isNaN(Number(values[i])) ? 0 : Number(values[i])])
) as Record<string, number>;
});
}
}
// Concrete miner for JSON (different format, same pipeline)
class JSONMiner extends DataMiner {
protected openFile(path: string): string {
console.log(`[JSON] Opening ${path}`);
return JSON.stringify([
{ sales: 80, returns: 4 },
{ sales: 120, returns: 6 },
]);
}
protected extractData(raw: string): string[] {
// Return raw JSON as a single-element array for parsing
return [raw];
}
protected parseData(data: string[]): Record<string, number>[] {
return JSON.parse(data[0]);
}
// Override hook to add extra step before reporting
protected beforeReport(): void {
console.log('[JSON] Validating results before report...');
}
}
// Usage — same interface, different internals
console.log('=== CSV Mining ===');
new CSVMiner().mine('data/sales.csv');
console.log('\n=== JSON Mining ===');
new JSONMiner().mine('data/sales.json');
When to Use
- Multiple classes share the same algorithm structure but differ in specific steps
- You want to avoid code duplication across related classes
- You want to control which parts of an algorithm subclasses can extend
- Hooks let subclasses optionally insert logic at specific points without making it mandatory
- Examples: data processing pipelines, game AI turns (move → attack → defend), test frameworks (
setUp/tearDown), web framework request lifecycle
Pros
- DRY — the invariant parts of the algorithm live in exactly one place
- Control over extension points — only the declared abstract/hook methods are overridable
- Easy to add new algorithm variants — just add a new subclass
Cons
- Template method is an inheritance-based pattern — some prefer composition (Strategy)
- Adding more variant steps means more abstract methods — subclass implementors must implement all of them
- The Liskov Substitution Principle can be violated if subclasses override more than intended
Related Patterns
- Strategy uses composition to swap entire algorithms; Template Method uses inheritance to vary steps within a fixed skeleton
- Factory Method is a specialisation of Template Method — a factory method is one abstract step in a template
- Hook Methods are a Template Method concept — optional steps subclasses can override