Skip to content

Template Method Pattern

Overview

The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class, allowing subclasses to override specific steps without changing the algorithm's structure. This pattern follows the "Hollywood Principle": "Don't call us, we'll call you" – the parent class calls the operations defined in subclasses.

Intent

  • Define the skeleton of an algorithm in an operation, deferring some steps to subclasses
  • Let subclasses redefine certain steps of an algorithm without changing its structure
  • Promote code reuse by factoring out common behavior into a base class
  • Ensure that the algorithm structure remains consistent across all implementations

Problem

When you have multiple classes that implement similar algorithms with slight variations, you often face:

  • Code duplication across similar implementations
  • Inconsistent algorithm structure
  • Difficulty maintaining common behavior
  • Risk of forgetting or misimplementing steps
  • Scattered variations of the same basic algorithm

Solution

Define an abstract class with a template method that outlines the algorithm's structure. The template method calls primitive operations (abstract methods) that subclasses must implement, and hook methods (optional methods with default implementations) that subclasses can override if needed.

Structure

Class Diagram

Template Method Pattern Diagram 0

Components

  1. AbstractClass

    • Defines the template method that calls primitive operations
    • Contains the algorithm skeleton
    • May provide hook methods with default implementations
    • Declares abstract primitive operations
  2. ConcreteClass

    • Implements the abstract primitive operations
    • Optionally overrides hook methods
    • Inherits the template method from AbstractClass
  3. Template Method

    • Defines the algorithm structure
    • Calls primitive operations and hooks in a specific order
    • Should be final (non-virtual) to prevent overriding
  4. Primitive Operations

    • Abstract methods that subclasses must implement
    • Represent varying parts of the algorithm
  5. Hook Methods

    • Methods with default (often empty) implementations
    • Provide optional extension points
    • Can be overridden by subclasses

Implementation Examples

Example 1: Data Processing Pipeline

Processing different file formats with a consistent pipeline.

Template Method Pattern Diagram 1

Pipeline Steps:

  1. Read Data (primitive) - Format-specific reading
  2. Process Data (primitive) - Format-specific processing
  3. Validate (hook) - Optional validation step
  4. Save Data (primitive) - Format-specific saving

Usage:

// CSV processing
CSVProcessor csvProcessor;
csvProcessor.process("data.csv");
// Output: Reads CSV -> Processes -> Validates -> Saves to DB

// JSON processing with custom validation
JSONProcessor jsonProcessor;
jsonProcessor.process("data.json");
// Output: Reads JSON -> Processes -> Custom validation -> Saves to NoSQL

// XML processing without validation
XMLProcessor xmlProcessor;
xmlProcessor.process("data.xml");
// Output: Reads XML -> Processes -> Skips validation -> Saves to XML DB

Example 2: Game AI Template

Different AI behaviors sharing the same turn structure.

Template Method Pattern Diagram 2

AI Turn Structure:

AI Type Collect Build Structures Build Units Attack
Aggressive Default Barracks, weapons Attack units Aggressive assault
Defensive Careful Walls, towers Defensive units Counter-attack only
Balanced Default Mixed structures Balanced army Default strategy

Usage:

AggressiveAI aggressive;
aggressive.takeTurn();
// Output: Collects -> Builds offensive -> Trains attackers -> Attacks!

DefensiveAI defensive;
defensive.takeTurn();
// Output: Carefully collects -> Builds defenses -> Trains guards -> Defends

BalancedAI balanced;
balanced.takeTurn();
// Output: Collects -> Builds mixed -> Balanced army -> Default attack

Example 3: Web Scraper Template

Scraping different content types with a consistent process.

Template Method Pattern Diagram 3

Scraping Process:

1
2
3
4
5
1. Fetch Page (common)
2. Authenticate (hook - if needed)
3. Extract Data (primitive - content-specific)
4. Clean Data (primitive - format-specific)
5. Save Data (primitive - storage-specific)

Usage:

1
2
3
4
5
6
7
ProductPriceScraper priceScraper;
priceScraper.scrape("https://example.com/products");
// Fetches -> Extracts prices -> Cleans -> Saves to price DB

NewsArticleScraper newsScraper;
newsScraper.scrape("https://example.com/news");
// Fetches -> Authenticates -> Extracts article -> Cleans -> Saves to article DB

Example 4: Report Generator

Generating reports in different formats with consistent structure.

Template Method Pattern Diagram 4

Report Structure:

Format Header TOC Content Charts Footer
PDF Logo + title Styled Pages + copyright
HTML CSS + nav Hyperlinked Contact + social
Text Simple border Plain text End marker

Usage:

PDFReport pdfReport;
pdfReport.generateReport("Annual Sales Report 2025");
// Header (logo) -> TOC -> Content (styled) -> Charts -> Footer (pages)

HTMLReport htmlReport;
htmlReport.generateReport("Quarterly Report");
// Header (CSS) -> Content (HTML) -> Charts -> Footer (links)

PlainTextReport textReport;
textReport.generateReport("Monthly Summary");
// Header (border) -> Content (plain) -> Footer (end marker)

Example 5: Unit Test Template

Testing framework with setUp-test-tearDown structure.

Template Method Pattern Diagram 5

Test Lifecycle:

1
2
3
1. setUp() - Prepare test environment
2. runTest() - Execute test logic
3. tearDown() - Clean up resources

Usage:

1
2
3
4
5
6
7
DatabaseConnectionTest dbTest;
dbTest.run();
// setUp (connect DB) -> runTest (test operations) -> tearDown (cleanup)

APIEndpointTest apiTest;
apiTest.run();
// setUp (mock server) -> runTest (API calls) -> tearDown (stop server)

Sequence Diagram

Template Method Pattern Diagram 6

Real-World Applications

1. Unit Testing Frameworks

JUnit / Google Test / PyTest Pattern:

class TestCase {
public:
    void run() {
        setUp();
        try {
            test();
            recordSuccess();
        } catch (const std::exception& e) {
            recordFailure(e);
        }
        tearDown();
    }
protected:
    virtual void setUp() {}     // Hook
    virtual void test() = 0;    // Primitive
    virtual void tearDown() {}  // Hook
};

Consistent across all tests: - setUp → test → tearDown structure - Exception handling - Result recording

2. Web Frameworks (Django, Flask, Express)

HTTP Request Handling:

class RequestHandler:
    def handle_request(self, request):
        # Template method
        self.parse_request(request)
        if self.should_authenticate():
            self.authenticate()
        response = self.process_request()
        self.finalize_response(response)
        return response

    def parse_request(self, request):     # Common
        pass

    def should_authenticate(self):         # Hook
        return False

    def process_request(self):             # Primitive
        raise NotImplementedError

    def finalize_response(self, response): # Common
        pass

3. Compilers and Interpreters

Compilation Pipeline:

Source Code
Lexical Analysis    (tokenize)
Syntax Analysis     (parse)
Semantic Analysis   (type check)
Optimization        (optional - hook)
Code Generation     (target-specific - primitive)
Target Code

Different backends (x86, ARM, LLVM) implement code generation differently but share the same pipeline.

4. Game Development

Game Loop:

class Game {
public:
    void run() {
        initialize();
        while (running) {
            processInput();
            update(deltaTime);
            render();
        }
        cleanup();
    }
protected:
    virtual void initialize() = 0;
    virtual void processInput() = 0;
    virtual void update(float dt) = 0;
    virtual void render() = 0;
    virtual void cleanup() = 0;
};

Every game follows: input → update → render, but each implements these steps differently.

5. Data Migration Tools

ETL (Extract, Transform, Load):

class DataMigration {
public:
    void migrate() {
        connect();
        data = extract();
        transformed = transform(data);
        if (shouldValidate()) {
            validate(transformed);
        }
        load(transformed);
        disconnect();
    }
protected:
    virtual void connect() = 0;
    virtual Data extract() = 0;
    virtual Data transform(Data d) = 0;
    virtual void load(Data d) = 0;
    virtual bool shouldValidate() { return true; }
    virtual void validate(Data d) {}
    virtual void disconnect() = 0;
};

6. Sorting Algorithms (std::sort)

C++ Standard Library:

template<typename RandomIt, typename Compare>
void sort(RandomIt first, RandomIt last, Compare comp) {
    // Template method (IntroSort)
    if (last - first > threshold) {
        quicksort(first, last, comp);  // Fast for large
    } else {
        insertion_sort(first, last, comp);  // Fast for small
    }
    // User provides comparison function (primitive operation)
}

7. Document Parsers

Parsing Different Formats:

1
2
3
4
5
6
7
8
Common Structure:
1. Open file
2. Read header (format-specific)
3. Parse body (format-specific)
4. Extract metadata (format-specific)
5. Close file

Formats: PDF, Word, Excel, XML, JSON

8. Database Connections

Connection Lifecycle:

class DatabaseConnection {
public:
    void execute(Query query) {
        open();                    // Primitive (DB-specific)
        authenticate();            // Primitive
        result = executeQuery(query);  // Primitive
        if (shouldCache()) {       // Hook
            cache(result);
        }
        close();                   // Primitive
        return result;
    }
};

// MySQL, PostgreSQL, MongoDB implementations

Design Considerations

Advantages

Code Reuse: Common algorithm structure is defined once in the base class.

// Instead of duplicating in every class:
class CSVProcessor {
    void process() {
        read(); process(); validate(); save();  // Duplicated
    }
};
class JSONProcessor {
    void process() {
        read(); process(); validate(); save();  // Duplicated
    }
};

// Define once:
class DataProcessor {
    void process() {  // Template method
        read(); process(); validate(); save();
    }
};

Consistent Structure: All implementations follow the same algorithm skeleton.

Inversion of Control (Hollywood Principle): "Don't call us, we'll call you" - parent calls child methods.

1
2
3
4
5
// Parent (AbstractClass) calls child methods
void templateMethod() {
    step1();           // Calls ConcreteClass::step1()
    step2();           // Calls ConcreteClass::step2()
}

Hook Methods: Optional customization points with defaults.

virtual bool shouldValidate() const { return true; }  // Default
virtual void hook() {}  // Empty default

Open/Closed Principle: Add new variants without modifying the template.

Disadvantages

Inheritance Dependency: Relies on inheritance, which limits flexibility.

// Can't easily combine behaviors from different hierarchies
// Stuck with single inheritance in C++

Liskov Substitution Violations: Subclasses might break expectations.

// Parent expects certain behavior
// Child might violate it by poor override

Difficult to Follow: Control flow goes up and down the hierarchy.

1
2
3
4
5
Client  ConcreteClass::templateMethod()
       AbstractClass::templateMethod()
       ConcreteClass::primitiveOp1()
       AbstractClass (continues)
       ConcreteClass::primitiveOp2()

Class Explosion: Each variant requires a new class.

Template Method vs Strategy Pattern

Aspect Template Method Strategy
Structure Inheritance-based Composition-based
Algorithm Skeleton in base class Entire algorithm in strategy
Binding Compile-time (static) Runtime (dynamic)
Flexibility Less flexible (inheritance) More flexible (composition)
Control Parent controls flow Client chooses strategy
Granularity Steps of algorithm Entire algorithm
Use case Common structure, varying steps Interchangeable algorithms

Template Method Example:

1
2
3
4
5
6
7
class DataProcessor {
    void process() {  // Fixed structure
        read();    // Varies
        clean();   // Varies
        save();    // Varies
    }
};

Strategy Example:

1
2
3
4
5
6
class Context {
    Strategy* strategy_;
    void execute() {
        strategy_->algorithm();  // Entire algorithm varies
    }
};

When to prefer each:

  • Template Method: When you have a well-defined algorithm structure with varying steps
  • Strategy: When you need to switch entire algorithms at runtime

When to Use Template Method

Use Template Method when:

  • Multiple classes implement similar algorithms with minor variations
  • You want to enforce a consistent algorithm structure
  • Common behavior should be factored out to avoid duplication
  • You're building a framework where users extend by overriding methods
  • The algorithm structure is stable, but steps vary

Good Candidates: - Testing frameworks (setUp/test/tearDown) - Data processing pipelines - Document generators - Game loops - Compilers

Avoid Template Method when:

  • Algorithm structure varies significantly between implementations
  • You need to switch algorithms at runtime
  • You prefer composition over inheritance
  • The hierarchy would become too deep or complex
  • Only a small part of the algorithm varies

Use Simpler Alternatives: - Strategy pattern for runtime flexibility - Function pointers/lambdas for simple variations - Composition for combining behaviors

Best Practices

1. Make Template Method Non-Virtual

class AbstractClass {
public:
    // Final - cannot be overridden
    void templateMethod() final {
        step1();
        step2();
    }
protected:
    virtual void step1() = 0;
    virtual void step2() = 0;
};

2. Use Descriptive Names

// Good
class DataProcessor {
    void processFile() {         // Clear purpose
        readFromDisk();
        transformData();
        validateSchema();
        writeToDatabase();
    }
};

// Bad
class DataProcessor {
    void execute() {             // Generic
        doStep1();
        doStep2();
        doStep3();
    }
};

3. Minimize Primitive Operations

// Too many primitive operations
class Report {
    void generate() {
        step1(); step2(); step3(); step4(); 
        step5(); step6(); step7(); step8();  // Too many!
    }
};

// Better - group related operations
class Report {
    void generate() {
        createHeader();    // Groups multiple steps
        addContent();      // Groups multiple steps
        createFooter();    // Groups multiple steps
    }
};

4. Document Override Requirements

class DataProcessor {
protected:
    /**
     * Read data from source.
     * @param filename Path to the file
     * @return Raw data string
     * @note Must be implemented by subclasses
     */
    virtual std::string readData(const std::string& filename) = 0;

    /**
     * Optional validation step.
     * @return true to validate, false to skip
     * @note Default returns true
     */
    virtual bool shouldValidate() const { return true; }
};

5. Provide Meaningful Hooks

class GameAI {
protected:
    // Hooks with clear purpose and good defaults
    virtual void onTurnStart() {}
    virtual void onTurnEnd() {}
    virtual bool shouldSkipDefense() const { return false; }

    void takeTurn() {
        onTurnStart();           // Hook
        collectResources();      // Required
        if (!shouldSkipDefense()) {  // Hook
            buildDefenses();
        }
        attack();                // Required
        onTurnEnd();             // Hook
    }
};

6. Use Protected Instead of Public

1
2
3
4
5
6
7
8
9
class AbstractClass {
public:
    void templateMethod() { /* ... */ }  // Public interface

protected:  // Not public!
    virtual void primitiveOp1() = 0;
    virtual void primitiveOp2() = 0;
    virtual void hook() {}
};

7. Consider Using NVI (Non-Virtual Interface)

class AbstractClass {
public:
    // Public non-virtual interface
    void templateMethod() {
        doBeforeHook();
        primitiveOp();
        doAfterHook();
    }

protected:
    // Protected virtual implementation
    virtual void doBeforeHook() {}
    virtual void primitiveOp() = 0;
    virtual void doAfterHook() {}
};
  • Strategy: Similar intent but uses composition instead of inheritance. Strategy is more flexible at runtime.
  • Factory Method: A special case of Template Method where one step is object creation.
  • Bridge: Separates abstraction from implementation; Template Method uses inheritance for variation.
  • Decorator: Adds behavior dynamically; Template Method defines behavior through inheritance.

Summary

The Template Method Pattern provides a powerful way to: - Define algorithm skeletons in base classes - Promote code reuse by factoring out common behavior - Ensure consistency across implementations - Control customization through primitive operations and hooks

Key Points:

  1. Inheritance-based - Uses class hierarchy
  2. Inversion of control - Parent calls child methods
  3. Compile-time binding - Variations determined at compile time
  4. Best for stable structures - Algorithm structure should be well-defined

When to Use: - Testing frameworks (setUp/test/tearDown) - Data processing pipelines with consistent steps - Document generators with fixed structure - Game loops with standard phases - Any algorithm with fixed structure but varying implementations

When to Avoid: - Need runtime algorithm switching (use Strategy) - Prefer composition over inheritance - Algorithm structure is unstable or varies significantly - Would result in deep inheritance hierarchies

The Template Method Pattern excels in framework design and situations where you want to define an algorithm's structure once and let subclasses fill in the details. However, be mindful of inheritance limitations and consider composition-based alternatives like Strategy when runtime flexibility is important.