Skip to main content

Command Palette

Search for a command to run...

Template Method Pattern

Updated
View as Markdown
Template Method Pattern

The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class and lets subclasses override specific steps—without changing the overall structure.

Think of a recipe. The steps are always the same: prepare ingredients, cook, plate, serve. But the actual ingredients and cooking technique vary by dish. The recipe is the template; each dish is a subclass.

The Problem

Imagine you're building a data report generator. Different reports (CSV, PDF, HTML) follow the same overall process but differ in specific steps:

// Without Template Method - massive duplication
class CSVReportGenerator {
    public void generateReport() {
        // Step 1: fetch data
        System.out.println("Fetching data from DB...");

        // Step 2: parse data (CSV-specific)
        System.out.println("Parsing data as CSV...");

        // Step 3: format (CSV-specific)
        System.out.println("Formatting as comma-separated values...");

        // Step 4: export (CSV-specific)
        System.out.println("Exporting as .csv file...");

        // Step 5: notify
        System.out.println("Sending notification...");
    }
}

class PDFReportGenerator {
    public void generateReport() {
        // Step 1: fetch data (DUPLICATED!)
        System.out.println("Fetching data from DB...");

        // Step 2-4: PDF-specific (duplicates structure)
        System.out.println("Parsing data as PDF...");
        System.out.println("Formatting with layout engine...");
        System.out.println("Exporting as .pdf file...");

        // Step 5: notify (DUPLICATED!)
        System.out.println("Sending notification...");
    }
}

Problems:

  • Code duplication: The overall algorithm is copy-pasted across classes

  • Hard to maintain: Fixing a shared step requires updating every class

  • Inconsistency risk: One class might forget a step entirely

  • No enforced structure: Nothing stops a subclass from skipping critical steps

The Solution: Template Method Pattern

The Template Method Pattern puts the algorithm skeleton in a base class and delegates varying steps to subclasses.

The pattern separates:

  • What stays the same (template method in base class)

  • What varies (overridden steps in subclasses)

  • What's optional (hooks — overridable but with default behavior)

How It Solves Each Problem

Problem How Template Method Solves It
Code duplication Shared steps live once in the base class template method.
Hard to maintain Fix a shared step in one place—all subclasses benefit.
Inconsistency risk The algorithm structure is enforced by the base class.
No enforced structure templateMethod() is final—subclasses can't reorder steps.

Key Components

  1. AbstractClass: Defines the templateMethod() and declares abstract/hook steps

  2. ConcreteClass: Implements the varying steps for a specific algorithm variant

  3. Template Method: The final method that orchestrates the algorithm

  4. Hook Methods: Optional overridable methods with default (often empty) implementations

How It Solves the Problem

Real-World Implementation

Example 1: Report Generator

// Abstract Class
abstract class ReportGenerator {

    // Template Method - final so subclasses cannot change the order
    public final void generateReport() {
        fetchData();
        parseData();
        formatReport();
        if (shouldExport()) {
            exportReport();
        }
        sendNotification();
    }

    // Shared steps - implemented in base class
    private void fetchData() {
        System.out.println("Fetching data from database...");
    }

    private void sendNotification() {
        System.out.println("Notification sent: report is ready.\n");
    }

    // Abstract steps - subclasses must implement
    protected abstract void parseData();
    protected abstract void formatReport();
    protected abstract void exportReport();

    // Hook - subclasses may override; default is true
    protected boolean shouldExport() {
        return true;
    }
}

// Concrete Class - CSV
class CSVReportGenerator extends ReportGenerator {
    @Override
    protected void parseData() {
        System.out.println("Parsing data as CSV rows...");
    }

    @Override
    protected void formatReport() {
        System.out.println("Formatting with comma separators...");
    }

    @Override
    protected void exportReport() {
        System.out.println("Exporting as report.csv");
    }
}

// Concrete Class - PDF
class PDFReportGenerator extends ReportGenerator {
    @Override
    protected void parseData() {
        System.out.println("Parsing data as PDF content blocks...");
    }

    @Override
    protected void formatReport() {
        System.out.println("Formatting with PDF layout engine...");
    }

    @Override
    protected void exportReport() {
        System.out.println("Exporting as report.pdf");
    }
}

// Concrete Class - HTML (with hook override)
class HTMLReportGenerator extends ReportGenerator {
    @Override
    protected void parseData() {
        System.out.println("Parsing data as HTML table rows...");
    }

    @Override
    protected void formatReport() {
        System.out.println("Formatting with HTML/CSS templates...");
    }

    @Override
    protected void exportReport() {
        System.out.println("Publishing to web server...");
    }

    @Override
    protected boolean shouldExport() {
        System.out.println("Checking server availability...");
        return true;
    }
}

// Client
public class ReportDemo {
    public static void main(String[] args) {
        System.out.println("=== CSV Report ===");
        new CSVReportGenerator().generateReport();

        System.out.println("=== PDF Report ===");
        new PDFReportGenerator().generateReport();

        System.out.println("=== HTML Report ===");
        new HTMLReportGenerator().generateReport();
    }
}

Example 2: Beverage Preparation (Classic GoF Example)

// Abstract Class
abstract class Beverage {

    // Template Method
    public final void prepare() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    // Shared steps
    private void boilWater() {
        System.out.println("Boiling water...");
    }

    private void pourInCup() {
        System.out.println("Pouring into cup...");
    }

    // Abstract steps
    protected abstract void brew();
    protected abstract void addCondiments();

    // Hook - customer can skip condiments
    protected boolean customerWantsCondiments() {
        return true;
    }
}

// Concrete Class - Tea
class Tea extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Steeping the tea bag...");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding lemon...");
    }
}

// Concrete Class - Coffee
class Coffee extends Beverage {
    @Override
    protected void brew() {
        System.out.println("Dripping coffee through filter...");
    }

    @Override
    protected void addCondiments() {
        System.out.println("Adding milk and sugar...");
    }

    @Override
    protected boolean customerWantsCondiments() {
        System.out.println("Customer wants black coffee - skipping condiments.");
        return false;
    }
}

// Client
public class BeverageDemo {
    public static void main(String[] args) {
        System.out.println("=== Making Tea ===");
        new Tea().prepare();

        System.out.println("\n=== Making Coffee ===");
        new Coffee().prepare();
    }
}

Example 3: Data Migration Pipeline

// Abstract Class
abstract class DataMigrationPipeline {

    // Template Method
    public final void migrate() {
        System.out.println("--- Starting Migration ---");
        connect();
        extractData();
        transformData();
        validateData();
        loadData();
        disconnect();
        System.out.println("--- Migration Complete ---\n");
    }

    // Fixed shared steps
    private void connect() {
        System.out.println("Connecting to source and target systems...");
    }

    private void disconnect() {
        System.out.println("Disconnecting from all systems.");
    }

    private void validateData() {
        System.out.println("Validating transformed data...");
    }

    // Abstract steps
    protected abstract void extractData();
    protected abstract void transformData();
    protected abstract void loadData();
}

// MySQL to PostgreSQL
class MySQLToPostgresPipeline extends DataMigrationPipeline {
    @Override
    protected void extractData() {
        System.out.println("Extracting data from MySQL tables...");
    }

    @Override
    protected void transformData() {
        System.out.println("Transforming MySQL data types to PostgreSQL...");
    }

    @Override
    protected void loadData() {
        System.out.println("Loading data into PostgreSQL schema...");
    }
}

// CSV to MongoDB
class CSVToMongoPipeline extends DataMigrationPipeline {
    @Override
    protected void extractData() {
        System.out.println("Reading rows from CSV files...");
    }

    @Override
    protected void transformData() {
        System.out.println("Transforming CSV rows into JSON documents...");
    }

    @Override
    protected void loadData() {
        System.out.println("Inserting documents into MongoDB collections...");
    }
}

// Client
public class MigrationDemo {
    public static void main(String[] args) {
        System.out.println("=== MySQL to PostgreSQL ===");
        new MySQLToPostgresPipeline().migrate();

        System.out.println("=== CSV to MongoDB ===");
        new CSVToMongoPipeline().migrate();
    }
}

Workflow Diagram

Real-World Use Cases

  • Report Generators: PDF, CSV, HTML reports with shared fetch/notify steps

  • Data Pipelines: ETL (Extract, Transform, Load) workflows

  • Web Frameworks: Spring's JdbcTemplate, RestTemplate

  • Test Frameworks: JUnit's setUp() / tearDown() lifecycle

  • Build Tools: Maven/Gradle build lifecycle phases

  • Game Loops: Initialize → update → render → cleanup

  • Authentication Flows: Shared validation with custom login strategies

When to Use the Template Method Pattern

✅ Use When

  1. Shared Algorithm Structure: Multiple classes follow the same steps

  2. Controlled Extension: You want subclasses to customize only specific steps

  3. Avoid Duplication: Common steps are duplicated across classes

  4. Framework Hooks: You provide a framework with extension points

❌ Avoid When

  1. Too Many Subclasses: If every step varies, consider Strategy Pattern instead

  2. Deep Inheritance: Inheritance chains become hard to follow

  3. Simple Algorithms: Overhead isn't justified for trivial cases

Benefits

  1. Eliminate Duplication: Shared steps live in one place

  2. Enforced Structure: Algorithm order guaranteed by final template method

  3. Open/Closed Principle: Add new variants without modifying the base class

  4. Hollywood Principle: "Don't call us, we'll call you"—base class controls flow

Drawbacks

  1. Inheritance Required: Must subclass, which increases coupling

  2. Liskov Risk: Subclasses must honor the base class contract

  3. Debugging Complexity: Flow jumps between base and subclasses

Template Method vs Strategy Pattern

Aspect Template Method Strategy
Mechanism Inheritance Composition
Variation Compile-time Runtime
Control Base class Client
Granularity Step-level Algorithm-level
Best For Fixed structure, varying steps Swappable full algorithms

Best Practices

  1. Make Template Method final: Prevent subclasses from reordering steps

  2. Minimize Abstract Methods: Only make steps abstract if they truly vary

  3. Use Hooks Wisely: Provide hooks for optional customization

  4. Name Methods Clearly: Step names should express intent, not implementation

  5. Prefer Narrow Overrides: Subclasses should override as little as possible

  6. Document the Template: Make the algorithm structure obvious with comments

Conclusion

The Template Method Pattern is one of the most elegant ways to enforce a consistent algorithm while allowing flexible customization. It's the backbone of countless frameworks and libraries—and once you see it, you'll spot it everywhere.

Remember: Define the skeleton, let subclasses fill in the flesh! 🦴


🎯 Key Takeaway

The Template Method Pattern is about defining the recipe once and letting subclasses choose the ingredients. When your algorithm structure is fixed but the steps vary—template it!


A junior dev asked, "Why is templateMethod() marked final?" The senior replied, "Because the last time I let someone override it, they skipped the validation step, deployed on Friday, and we spent the weekend undoing it." The junior slowly closed the PR. 😄

Happy Templating! 🦴✨