lesson

Introduction to Template Method Pattern

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:

typescript
1class PDFReportGenerator {
2 generateReport(data: ReportData): string {
3 // Collect data
4 const reportData = this.fetchData(data);
5
6 // PDF-specific formatting
7 let output = '%PDF-1.4\n';
8 output += this.formatPDFHeader(reportData);
9 output += this.formatPDFData(reportData);
10 output += this.formatPDFFooter(reportData);
11
12 return output;
13 }
14}
15
16class CSVReportGenerator {
17 generateReport(data: ReportData): string {
18 // Collect data (duplicated!)
19 const reportData = this.fetchData(data);
20
21 // CSV-specific formatting
22 let output = this.formatCSVHeader(reportData);
23 output += this.formatCSVData(reportData);
24 output += this.formatCSVFooter(reportData);
25
26 return output;
27 }
28}
29
30class ExcelReportGenerator {
31 generateReport(data: ReportData): string {
32 // Collect data (duplicated again!)
33 const reportData = this.fetchData(data);
34
35 // Excel-specific formatting
36 let output = this.formatExcelHeader(reportData);
37 output += this.formatExcelData(reportData);
38 output += this.formatExcelFooter(reportData);
39
40 return output;
41 }
42}

Problems with this Approach

  1. Duplicated Algorithm Structure: The overall flow (fetch, header, data, footer) is repeated
  2. Hard to Maintain: Changes to the algorithm require modifying all classes
  3. Violates DRY Principle: Common steps like data fetching are duplicated
  4. No Enforcement: Nothing ensures all formats follow the same structure
  5. 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:

  1. Defining the Algorithm Skeleton: The base class defines the overall structure
  2. Abstract Steps: Subclasses implement format-specific steps
  3. Hook Methods: Optional customization points
  4. Common Logic: Shared code stays in the base class
  5. 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 │ extends
19 ┌───────────────┼───────────────┐
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:

typescript
1abstract class ReportGenerator {
2 // Template method - defines the algorithm structure
3 generateReport(data: ReportData): string {
4 // Common steps
5 const processedData = this.collectData(data);
6 this.validateData(processedData);
7
8 // Format-specific steps (abstract methods)
9 let output = this.formatHeader(processedData);
10 output += this.formatData(processedData);
11 output += this.formatFooter(processedData);
12
13 // Optional hook
14 output = this.postProcess(output);
15
16 return output;
17 }
18
19 // Common method (implemented in base class)
20 protected collectData(data: ReportData): ProcessedData {
21 // Shared data collection logic
22 return { /* ... */ };
23 }
24
25 // 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 }
31
32 // 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;
36
37 // Hook method (optional override)
38 protected postProcess(output: string): string {
39 return output; // Default: no post-processing
40 }
41}
42
43class PDFReportGenerator extends ReportGenerator {
44 protected formatHeader(data: ProcessedData): string {
45 return `%PDF-1.4\nTitle: ${data.title}\n\n`;
46 }
47
48 protected formatData(data: ProcessedData): string {
49 return data.rows.map(row => `| ${row.join(' | ')} |`).join('\n');
50 }
51
52 protected formatFooter(data: ProcessedData): string {
53 return `\n\nGenerated: ${new Date().toISOString()}`;
54 }
55}
56
57class CSVReportGenerator extends ReportGenerator {
58 protected formatHeader(data: ProcessedData): string {
59 return data.columns.join(',') + '\n';
60 }
61
62 protected formatData(data: ProcessedData): string {
63 return data.rows.map(row => row.join(',')).join('\n');
64 }
65
66 protected formatFooter(data: ProcessedData): string {
67 return ''; // CSV typically has no footer
68 }
69}

Benefits of Template Method Pattern

  1. Code Reuse: Common algorithm steps are defined once
  2. Consistency: All subclasses follow the same structure
  3. Maintainability: Algorithm changes happen in one place
  4. Open/Closed Principle: New formats can be added without modifying the base class
  5. Inversion of Control: Framework calls subclass methods ("Hollywood Principle")
  6. 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

typescript
1// Common pipeline: validate → transform → load
2abstract 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

typescript
1// Common flow: validate → calculate → fulfill → notify
2abstract 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

typescript
1// Common structure: header → content → footer
2abstract 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

AspectTemplate MethodStrategy
StructureUses inheritanceUses composition
FlexibilityFixed algorithm structureEntire algorithm is replaceable
ControlBase class controls the flowClient controls which strategy
GranularityOverride individual stepsReplace entire algorithm
Best forSimilar algorithms with varying stepsDifferent algorithms

Key Takeaways

  1. Template Method defines algorithm skeleton in base class
  2. Abstract methods require subclass implementation
  3. Hook methods provide optional customization points
  4. Common steps are implemented once in the base class
  5. 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.