The Template Method pattern defines the skeleton of an algorithm in a base class, deferring some steps to subclasses. It lets subclasses redefine certain steps without changing the algorithm’s structure. The base class controls the flow; subclasses fill in the details.

Intent and Motivation

Intent: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

Motivation: Building a house follows a fixed sequence: lay foundation → build walls → install roof. A wooden house and a glass house share the sequence but differ in materials. Template Method encodes the invariant steps in a base class buildHouse() and declares abstract buildWalls() and buildRoof() for subclasses. The “Hollywood Principle” applies: “Don’t call us, we’ll call you” — the base class calls subclass hooks, not the other way around.

This pattern is ubiquitous in frameworks that define lifecycle hooks: test runners, servlet processing, and data pipeline ETL.

Structure (UML-like)

  ┌──────────────────────┐
│   AbstractClass      │
├──────────────────────┤
│ + templateMethod()     │  (final — defines algorithm skeleton)
│ # primitiveOp1()       │  (abstract — subclass implements)
│ # primitiveOp2()       │  (abstract — subclass implements)
│ # hook()               │  (optional override — default empty)
└──────────▲───────────┘
           │ extends
┌──────────┴───────────┐
│   ConcreteClass      │
├──────────────────────┤
│ # primitiveOp1()     │  (specific implementation)
│ # primitiveOp2()     │  (specific implementation)
└──────────────────────┘
  

Participants:

  • AbstractClass — defines templateMethod() calling primitive operations and hooks.
  • ConcreteClass — implements primitive operations for specific behavior.
  • Client — uses the ConcreteClass through the AbstractClass interface.

Java Example

  abstract class DataMiner {
    // Template method — defines the algorithm skeleton
    public final void mine(String path) {
        String raw = openFile(path);
        String data = extractData(raw);
        if (shouldAnalyze()) {
            String analysis = analyze(data);
            sendReport(analysis);
        }
        closeFile();
    }

    protected abstract String openFile(String path);
    protected abstract String extractData(String raw);
    protected abstract void closeFile();

    protected String analyze(String data) {
        return "Default analysis of " + data.length() + " chars";
    }

    protected boolean shouldAnalyze() { return true; } // hook
    protected void sendReport(String report) {
        System.out.println("Report: " + report);
    }
}

class PDFMiner extends DataMiner {
    protected String openFile(String path) {
        System.out.println("Opening PDF: " + path);
        return "pdf-raw-content";
    }
    protected String extractData(String raw) {
        return "Extracted text from PDF";
    }
    protected void closeFile() { System.out.println("Closing PDF"); }
}

class CSVMiner extends DataMiner {
    protected String openFile(String path) {
        System.out.println("Opening CSV: " + path);
        return "col1,col2,col3";
    }
    protected String extractData(String raw) {
        return "Parsed " + raw.split(",").length + " columns";
    }
    protected void closeFile() { System.out.println("Closing CSV"); }

    @Override
    protected boolean shouldAnalyze() { return false; } // skip analysis
}

// Usage
new PDFMiner().mine("report.pdf");
new CSVMiner().mine("data.csv");
  

JavaScript Example

  class CoffeeMaker {
  // Template method
  make() {
    this.boilWater();
    this.brew();
    this.pourInCup();
    if (this.customerWantsCondiments()) {
      this.addCondiments();
    }
    console.log('Coffee ready!');
  }

  boilWater() { console.log('Boiling water'); }
  pourInCup() { console.log('Pouring into cup'); }
  customerWantsCondiments() { return true; } // hook

  // Primitive operations — subclasses must implement
  brew() { throw new Error('Implement brew()'); }
  addCondiments() { throw new Error('Implement addCondiments()'); }
}

class EspressoMaker extends CoffeeMaker {
  brew() { console.log('Brewing espresso'); }
  addCondiments() { console.log('Adding sugar'); }
}

class LatteMaker extends CoffeeMaker {
  brew() { console.log('Brewing strong espresso'); }
  addCondiments() { console.log('Adding steamed milk'); }

  customerWantsCondiments() { return false; } // hook override
}

new EspressoMaker().make();
new LatteMaker().make();
  

Real-World Use Cases

Framework / System Usage
JUnit @BeforeEach / @Test / @AfterEach Test runner template: setup → run test → teardown.
Spring JdbcTemplate Fixed JDBC flow: get connection → execute → handle results → release.
Servlet service() method Template dispatches to doGet() / doPost() based on HTTP method.
Hibernate lifecycle callbacks @PrePersist, @PostLoad — hooks in the entity lifecycle template.
React class components componentDidMount → render → componentWillUnmount lifecycle template.
Build tools (Make, Gradle) Fixed phases: compile → test → package → deploy with customizable tasks.

Pros and Cons

Pros Cons
Reuses common algorithm structure across subclasses Inheritance-based — less flexible than composition (Strategy)
Subclasses focus only on variant steps Subclass proliferation for many algorithm variations
Controls extension points via abstract methods and hooks final template method can frustrate frameworks needing full override
Hollywood Principle — base class drives the flow Deep inheritance hierarchies can be hard to understand
Inversion of control — framework calls your code Hard to change algorithm structure without modifying base class

When to Use vs When NOT to Use

Use when:

  • Multiple classes share the same algorithm structure but differ in specific steps.
  • You want to control subclass extensions by defining invariant steps.
  • Common behavior should be centralized to avoid duplication.
  • You are building a framework with lifecycle hooks for user code.

Do NOT use when:

  • Algorithms differ entirely in structure, not just individual steps (use Strategy).
  • Only one implementation exists — a single class with all logic is simpler.
  • Composition and delegation are preferred over inheritance (Strategy is more flexible).
  • Runtime algorithm switching is needed (Template Method is compile-time via subclassing).

Common Mistakes

  1. Not marking template method final — subclasses override the skeleton, breaking the invariant flow.
  2. Too many abstract methods — if most steps vary, Strategy may be a better fit.
  3. Hooks with side effects — hook methods should be simple predicates or light overrides.
  4. Leaking primitive operation details — subclasses should not call each other’s primitive ops directly.
  5. Confusing with Strategy — Template Method uses inheritance for partial customization; Strategy uses composition for full algorithm swap.
  • Strategy — alternative using composition; swaps entire algorithms at runtime.
  • Factory Method — Template Method often calls Factory Methods for object creation steps.
  • Hook methods — empty or default methods in Template Method are the “Hollywood Principle” hooks.
  • Command — can encapsulate template method steps as command objects for undo support.
  • Observer — template method can notify observers at defined hook points.