Introduction to Template Method Pattern
What is the Template Method Pattern?
The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class, but lets subclasses override specific steps of the algorithm without changing its structure.
In essence, the Template Method pattern allows you to define the overall algorithm structure while deferring some steps to subclasses.
The Problem
Imagine you're building a report generation system for a logistics platform. Your system needs to generate reports in different formats:
- PDF Reports: Professional format for client delivery
- CSV Reports: Data exports for analysis
- Excel Reports: Detailed spreadsheets with formatting
All reports follow the same basic structure, but each format has different implementation details:
Without the Template Method pattern, you might write code like this:
typescript1class PDFReportGenerator {2 generateReport(data: ReportData): string {3 // Collect data4 const reportData = this.fetchData(data);56 // PDF-specific formatting7 let output = '%PDF-1.4\n';8 output += this.formatPDFHeader(reportData);9 output += this.formatPDFData(reportData);10 output += this.formatPDFFooter(reportData);1112 return output;13 }14}1516class CSVReportGenerator {17 generateReport(data: ReportData): string {18 // Collect data (duplicated!)19 const reportData = this.fetchData(data);2021 // CSV-specific formatting22 let output = this.formatCSVHeader(reportData);23 output += this.formatCSVData(reportData);24 output += this.formatCSVFooter(reportData);2526 return output;27 }28}2930class ExcelReportGenerator {31 generateReport(data: ReportData): string {32 // Collect data (duplicated again!)33 const reportData = this.fetchData(data);3435 // Excel-specific formatting36 let output = this.formatExcelHeader(reportData);37 output += this.formatExcelData(reportData);38 output += this.formatExcelFooter(reportData);3940 return output;41 }42}
Problems with this Approach
- Duplicated Algorithm Structure: The overall flow (fetch, header, data, footer) is repeated
- Hard to Maintain: Changes to the algorithm require modifying all classes
- Violates DRY Principle: Common steps like data fetching are duplicated
- No Enforcement: Nothing ensures all formats follow the same structure
- Difficult to Add Steps: Adding a new step (e.g., validation) requires changes everywhere
The Solution: Template Method Pattern
The Template Method pattern solves these issues by:
- Defining the Algorithm Skeleton: The base class defines the overall structure
- Abstract Steps: Subclasses implement format-specific steps
- Hook Methods: Optional customization points
- Common Logic: Shared code stays in the base class
- Enforced Structure: All subclasses follow the same flow
Pattern Structure
1┌─────────────────────────────────────────────────────────────┐2│ AbstractClass │3│ │4│ ┌──────────────────────────────────────────────┐ │5│ │ + templateMethod(): void │ │6│ │ # primitiveOperation1(): void (abstract) │ │7│ │ # primitiveOperation2(): void (abstract) │ │8│ │ # hook(): void (optional) │ │9│ └──────────────────────────────────────────────┘ │10│ │11│ templateMethod(): │12│ 1. commonOperation() │13│ 2. primitiveOperation1() │14│ 3. primitiveOperation2() │15│ 4. hook() │16└─────────────────────────────────────────────────────────────┘17 △18 │ extends19 ┌───────────────┼───────────────┐20 │ │ │21┌───────────────────┐ ┌──────────────┐ ┌──────────────┐22│ ConcreteClassA │ │ ConcreteClassB│ │ ConcreteClassC│23├───────────────────┤ ├──────────────┤ ├──────────────┤24│ + primitive1() │ │+ primitive1()│ │+ primitive1()│25│ + primitive2() │ │+ primitive2()│ │+ primitive2()│26│ + hook() │ │ │ │+ hook() │27└───────────────────┘ └──────────────┘ └──────────────┘
Logistics Context: Report Generation
Let's see how different report formats work with the same structure:
1. PDF Report Generator
Generates professional PDF documents:
- Header: PDF metadata and title formatting
- Data: Table with borders and styling
- Footer: Page numbers and timestamps
- Use case: Client-facing delivery reports, invoices
2. CSV Report Generator
Generates comma-separated value files:
- Header: Column names row
- Data: Comma-delimited data rows
- Footer: Optional summary row
- Use case: Data export for analysis, spreadsheet import
3. Excel Report Generator
Generates Excel spreadsheet files:
- Header: Styled header row with bold text
- Data: Formatted cells with data types
- Footer: Formula-based totals
- Use case: Financial reports, detailed analytics
Template Method Example
Here's how the Template Method pattern structures report generation:
typescript1abstract class ReportGenerator {2 // Template method - defines the algorithm structure3 generateReport(data: ReportData): string {4 // Common steps5 const processedData = this.collectData(data);6 this.validateData(processedData);78 // Format-specific steps (abstract methods)9 let output = this.formatHeader(processedData);10 output += this.formatData(processedData);11 output += this.formatFooter(processedData);1213 // Optional hook14 output = this.postProcess(output);1516 return output;17 }1819 // Common method (implemented in base class)20 protected collectData(data: ReportData): ProcessedData {21 // Shared data collection logic22 return { /* ... */ };23 }2425 // Common method (implemented in base class)26 protected validateData(data: ProcessedData): void {27 if (!data.rows || data.rows.length === 0) {28 throw new Error('No data to generate report');29 }30 }3132 // Abstract methods (must be implemented by subclasses)33 protected abstract formatHeader(data: ProcessedData): string;34 protected abstract formatData(data: ProcessedData): string;35 protected abstract formatFooter(data: ProcessedData): string;3637 // Hook method (optional override)38 protected postProcess(output: string): string {39 return output; // Default: no post-processing40 }41}4243class PDFReportGenerator extends ReportGenerator {44 protected formatHeader(data: ProcessedData): string {45 return `%PDF-1.4\nTitle: ${data.title}\n\n`;46 }4748 protected formatData(data: ProcessedData): string {49 return data.rows.map(row => `| ${row.join(' | ')} |`).join('\n');50 }5152 protected formatFooter(data: ProcessedData): string {53 return `\n\nGenerated: ${new Date().toISOString()}`;54 }55}5657class CSVReportGenerator extends ReportGenerator {58 protected formatHeader(data: ProcessedData): string {59 return data.columns.join(',') + '\n';60 }6162 protected formatData(data: ProcessedData): string {63 return data.rows.map(row => row.join(',')).join('\n');64 }6566 protected formatFooter(data: ProcessedData): string {67 return ''; // CSV typically has no footer68 }69}
Benefits of Template Method Pattern
- Code Reuse: Common algorithm steps are defined once
- Consistency: All subclasses follow the same structure
- Maintainability: Algorithm changes happen in one place
- Open/Closed Principle: New formats can be added without modifying the base class
- Inversion of Control: Framework calls subclass methods ("Hollywood Principle")
- Clear Contract: Abstract methods make requirements explicit
When to Use Template Method Pattern
Use the Template Method pattern when:
- You have multiple classes with similar algorithms that differ in specific steps
- You want to control which steps subclasses can override
- You want to avoid code duplication in similar algorithms
- You need to enforce a specific algorithm structure
- Common behavior should be factored out into a base class
When Not to Use Template Method Pattern
Avoid the Template Method pattern when:
- Algorithms are completely different (no common structure)
- You need more flexibility than inheritance allows (consider Strategy pattern)
- The template has too many steps (becomes rigid and hard to understand)
- Subclasses need to change the algorithm structure itself
Real-World Examples
1. Data Import Pipeline
typescript1// Common pipeline: validate → transform → load2abstract class DataImporter {3 import(file: File): void {4 const raw = this.readFile(file);5 this.validate(raw);6 const transformed = this.transform(raw);7 this.load(transformed);8 }9}
2. Order Processing
typescript1// Common flow: validate → calculate → fulfill → notify2abstract class OrderProcessor {3 process(order: Order): void {4 this.validateOrder(order);5 this.calculateTotals(order);6 this.fulfill(order);7 this.notifyCustomer(order);8 }9}
3. Document Generation
typescript1// Common structure: header → content → footer2abstract class DocumentGenerator {3 generate(data: Data): Document {4 const doc = this.createDocument();5 this.addHeader(doc, data);6 this.addContent(doc, data);7 this.addFooter(doc, data);8 return doc;9 }10}
Template Method vs Strategy Pattern
| Aspect | Template Method | Strategy |
|---|---|---|
| Structure | Uses inheritance | Uses composition |
| Flexibility | Fixed algorithm structure | Entire algorithm is replaceable |
| Control | Base class controls the flow | Client controls which strategy |
| Granularity | Override individual steps | Replace entire algorithm |
| Best for | Similar algorithms with varying steps | Different algorithms |
Key Takeaways
- Template Method defines algorithm skeleton in base class
- Abstract methods require subclass implementation
- Hook methods provide optional customization points
- Common steps are implemented once in the base class
- Perfect for logistics where processes follow similar structures but vary in details (reports, validations, workflows)
In the next lesson, we'll implement the Template Method pattern in TypeScript and explore abstract classes, hook methods, and final methods.