Low-level design3 min
Template Method
Type: Behavioral Pattern
Relevance: Medium/High. Very common in frameworks and libraries where the framework controls the lifecycle, but lets you hook into specific steps.
The Template Method pattern defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its overall structure.
Real-Life Analogy
Think of building a Mass-Produced House. Every house built by the developer follows the exact same architectural steps:
- Pour the foundation.
- Build the walls.
- Add the roof.
The developer provides a template. However, you (the buyer) can customize the walls (brick vs wood) and the roof (shingles vs metal). You cannot change the order—you cannot add the roof before pouring the foundation. The overall algorithm is fixed, but specific steps are customizable.
The Problem
You have multiple classes that contain almost identical algorithms, but with minor differences. If the core algorithm needs a bug fix, you have to go into all the different classes and fix it in every single one (violating DRY - Don't Repeat Yourself).
How to Implement Template Method
Create an abstract Base Class. Define a final (non-overridable) method that contains the algorithm sequence. The steps of the sequence are calls to other abstract or hook methods. Subclasses implement the abstract methods to provide concrete behavior.
Example in Code
// 1. The Abstract Class
abstract class DataMiner {
// THE TEMPLATE METHOD
// This method defines the algorithm skeleton.
// In a language like Java, you would mark this as `final` so subclasses can't break the sequence.
public mine(path: string): void {
const rawData = this.openFile(path);
const parsedData = this.extractData(rawData);
const analysis = this.analyzeData(parsedData);
this.sendReport(analysis);
}
// Common step: Implemented in the base class.
protected openFile(path: string): string {
console.log(`Opening file at ${path}...`);
return "raw_data";
}
// Abstract steps: MUST be implemented by subclasses.
protected abstract extractData(data: string): string;
protected abstract analyzeData(data: string): string;
// Hook method: Has a default empty implementation, subclasses CAN override it.
protected sendReport(analysis: string): void {
console.log(`Default Report: ${analysis}`);
}
}
// 2. Concrete Classes
class PDFDataMiner extends DataMiner {
protected extractData(data: string): string {
console.log("Extracting data from PDF format.");
return "pdf_parsed_data";
}
protected analyzeData(data: string): string {
console.log("Analyzing PDF data.");
return "pdf_analysis";
}
}
class CSVDataMiner extends DataMiner {
protected extractData(data: string): string {
console.log("Extracting data from CSV format.");
return "csv_parsed_data";
}
protected analyzeData(data: string): string {
console.log("Analyzing CSV data.");
return "csv_analysis";
}
// Overriding the hook method
protected sendReport(analysis: string): void {
console.log(`Custom CSV Report sent via Email: ${analysis}`);
}
}
// Client Code
console.log("--- Mining PDF ---");
const pdfMiner = new PDFDataMiner();
pdfMiner.mine("report.pdf");
console.log("\n--- Mining CSV ---");
const csvMiner = new CSVDataMiner();
csvMiner.mine("data.csv");
Class Diagram
Why is it Important?
- Code Reuse (DRY): You pull the common algorithm up into a superclass, eliminating duplication.
- Inversion of Control (Hollywood Principle): "Don't call us, we'll call you." The superclass controls the flow and calls the subclass methods, rather than the subclass trying to control everything.