Design Patterns: Template Method

8 min
Software DesignDesign PatternsOOP

Template Method defines the skeleton of an algorithm in a base class. Subclasses override specific steps to customize behavior without touching the overall flow.


Intent

Imagine a reporting system where each report type goes through the same steps: fetch data, process it, render output. But the actual content of each step varies by report type.

Without abstraction, each report class duplicates the same flow skeleton. Template Method locks the flow in the base class and lets subclasses implement only the variable parts.


Structure

  • AbstractClass: defines the template method (the flow skeleton) and abstract steps
  • templateMethod(): calls the steps in order; not overridable
  • abstractStep(): hooks that subclasses must implement
  • ConcreteClass: provides the step implementations

Practical Example: Report Generation Pipeline

TypeScript
abstract class ReportGenerator {
  // Template method: fixed flow
  generate(): string {
    const data = this.fetchData();
    const processed = this.processData(data);
    const header = this.renderHeader();
    const body = this.renderBody(processed);
    const footer = this.renderFooter();
    return [header, body, footer].join('\n');
  }

  // Abstract steps: subclasses implement these
  protected abstract fetchData(): Record[];
  protected abstract renderBody(data: Record[]): string;

  // Default implementations: subclasses may override
  protected processData(data: Record[]): Record[] {
    return data;
  }

  protected renderHeader(): string {
    return '=== Report ===';
  }

  protected renderFooter(): string {
    return `Generated at ${new Date().toISOString()}`;
  }
}

class SalesReport extends ReportGenerator {
  protected fetchData(): Record[] {
    return [
      { product: 1, revenue: 12000 },
      { product: 2, revenue: 8500 },
    ];
  }

  protected processData(data: Record[]): Record[] {
    return [...data].sort((a, b) => b['revenue'] - a['revenue']);
  }

  protected renderBody(data: Record[]): string {
    return data.map(row => `Product ${row['product']}: $${row['revenue']}`).join('\n');
  }

  protected renderHeader(): string {
    return '=== Sales Report ===';
  }
}

class InventoryReport extends ReportGenerator {
  protected fetchData(): Record[] {
    return [
      { sku: 1001, stock: 45 },
      { sku: 1002, stock: 3 },
    ];
  }

  protected renderBody(data: Record[]): string {
    return data
      .map(row => {
        const warning = row['stock'] < 10 ? ' [LOW STOCK]' : '';
        return `SKU ${row['sku']}: ${row['stock']} units${warning}`;
      })
      .join('\n');
  }
}

const sales = new SalesReport();
console.log(sales.generate());

const inventory = new InventoryReport();
console.log(inventory.generate());

When to Use

Good fits

  • Multiple classes share the same execution flow, but differ in individual steps
  • You want to enforce an invariant structure while allowing subclasses to customize specifics

Template Method vs. Strategy

Template MethodStrategy
Flow skeletonFixed in base classFully replaceable
MechanismInheritanceComposition
Swap timingCompile timeRuntime

Summary

Template Method is inversion of control at the class level: the base class calls the subclass, not the other way around.

When multiple classes share the same process flow but differ in a few steps, Template Method eliminates the duplication cleanly.