Skip to content Skip to footer

Template Method Design Pattern: Defining Algorithm Skeletons

You’re building a data processing system with multiple report generators. Each report follows the same steps: fetch data, process data, format output, send notification. The steps are the same, but the implementation differs. The Template Method pattern defines the skeleton of an algorithm, letting subclasses override specific steps without changing the algorithm’s structure.

Problem

Consider report generation:

public class PdfReportGenerator
{
    public void GenerateReport()
    {
        var data = FetchData();
        var processed = ProcessData(data);
        var formatted = FormatOutput(processed);
        SendNotification(formatted);
    }

    private Data FetchData() { /* PDF-specific */ }
    private Data ProcessData(Data data) { /* PDF-specific */ }
    private string FormatOutput(Data data) { /* PDF-specific */ }
    private void SendNotification(string output) { /* PDF-specific */ }
}

public class ExcelReportGenerator
{
    public void GenerateReport()
    {
        var data = FetchData();
        var processed = ProcessData(data);
        var formatted = FormatOutput(processed);
        SendNotification(formatted);
    }

    private Data FetchData() { /* Excel-specific */ }
    private Data ProcessData(Data data) { /* Excel-specific */ }
    private string FormatOutput(Data data) { /* Excel-specific */ }
    private void SendNotification(string output) { /* Excel-specific */ }
}

The algorithm structure is identical, but code is duplicated. If you need to change the algorithm (add a step, reorder steps), you must update every class.

Solution

The Template Method pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It lets subclasses redefine certain steps without changing the algorithm’s structure.

Definition: The Template Method pattern defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.

The template method in the base class defines the algorithm structure, while specific steps are implemented by subclasses.

Pseudocode

AbstractClass
    ↓ defines
TemplateMethod() {
    Step1() // abstract
    Step2() // abstract
    Step3() // concrete (optional)
}
    ↓ implemented by
ConcreteClass1, ConcreteClass2
    ↓ each implements
Step1(), Step2()

Examples

Here’s a complete, runnable C# implementation:

using System;

// Abstract class with template method
public abstract class DataProcessor
{
    // Template method - defines algorithm skeleton
    public void Process()
    {
        ReadData();
        ProcessData();
        SaveData();
        SendNotification();
    }

    // Abstract steps - must be implemented by subclasses
    protected abstract void ReadData();
    protected abstract void ProcessData();

    // Concrete steps - can be overridden if needed
    protected virtual void SaveData()
    {
        Console.WriteLine("Saving data to default location...");
    }

    protected virtual void SendNotification()
    {
        Console.WriteLine("Sending default notification...");
    }
}

// Concrete implementations
public class DatabaseProcessor : DataProcessor
{
    protected override void ReadData()
    {
        Console.WriteLine("Reading data from database...");
    }

    protected override void ProcessData()
    {
        Console.WriteLine("Processing database data...");
    }

    protected override void SaveData()
    {
        Console.WriteLine("Saving processed data back to database...");
    }
}

public class FileProcessor : DataProcessor
{
    protected override void ReadData()
    {
        Console.WriteLine("Reading data from file...");
    }

    protected override void ProcessData()
    {
        Console.WriteLine("Processing file data...");
    }

    protected override void SendNotification()
    {
        Console.WriteLine("Sending email notification with file results...");
    }
}

public class ApiProcessor : DataProcessor
{
    protected override void ReadData()
    {
        Console.WriteLine("Fetching data from API...");
    }

    protected override void ProcessData()
    {
        Console.WriteLine("Processing API data...");
    }

    protected override void SaveData()
    {
        Console.WriteLine("Saving data to cloud storage...");
    }

    protected override void SendNotification()
    {
        Console.WriteLine("Sending webhook notification...");
    }
}

// Usage
class Program
{
    static void Main()
    {
        Console.WriteLine("Database Processor:");
        var dbProcessor = new DatabaseProcessor();
        dbProcessor.Process();

        Console.WriteLine("\nFile Processor:");
        var fileProcessor = new FileProcessor();
        fileProcessor.Process();

        Console.WriteLine("\nAPI Processor:");
        var apiProcessor = new ApiProcessor();
        apiProcessor.Process();
    }
}

Output:

Database Processor:
Reading data from database...
Processing database data...
Saving processed data back to database...
Sending default notification...

File Processor:
Reading data from file...
Processing file data...
Saving data to default location...
Sending email notification with file results...

API Processor:
Fetching data from API...
Processing API data...
Saving data to cloud storage...
Sending webhook notification...

Real-World Example: Report Generation

public abstract class ReportGenerator
{
    // Template method
    public void GenerateReport()
    {
        var data = FetchData();
        var processed = ProcessData(data);
        var formatted = FormatReport(processed);
        SaveReport(formatted);
        SendReport(formatted);
    }

    protected abstract string FetchData();
    protected abstract string ProcessData(string data);
    protected abstract string FormatReport(string data);

    protected virtual void SaveReport(string report)
    {
        Console.WriteLine($"Saving report to default location...");
    }

    protected virtual void SendReport(string report)
    {
        Console.WriteLine("Report generated successfully");
    }
}

public class PdfReportGenerator : ReportGenerator
{
    protected override string FetchData()
    {
        Console.WriteLine("Fetching data for PDF report...");
        return "PDF Data";
    }

    protected override string ProcessData(string data)
    {
        Console.WriteLine("Processing data for PDF format...");
        return $"Processed {data}";
    }

    protected override string FormatReport(string data)
    {
        Console.WriteLine("Formatting as PDF...");
        return $"PDF: {data}";
    }

    protected override void SaveReport(string report)
    {
        Console.WriteLine($"Saving PDF report to file system...");
    }
}

public class ExcelReportGenerator : ReportGenerator
{
    protected override string FetchData()
    {
        Console.WriteLine("Fetching data for Excel report...");
        return "Excel Data";
    }

    protected override string ProcessData(string data)
    {
        Console.WriteLine("Processing data for Excel format...");
        return $"Processed {data}";
    }

    protected override string FormatReport(string data)
    {
        Console.WriteLine("Formatting as Excel...");
        return $"Excel: {data}";
    }

    protected override void SaveReport(string report)
    {
        Console.WriteLine($"Saving Excel report to file system...");
    }

    protected override void SendReport(string report)
    {
        Console.WriteLine("Emailing Excel report...");
    }
}

Game Framework Example

public abstract class Game
{
    // Template method
    public void Play()
    {
        Initialize();
        StartPlay();
        EndPlay();
    }

    protected abstract void Initialize();
    protected abstract void StartPlay();
    protected abstract void EndPlay();
}

public class Chess : Game
{
    protected override void Initialize()
    {
        Console.WriteLine("Chess: Setting up board and pieces");
    }

    protected override void StartPlay()
    {
        Console.WriteLine("Chess: Starting game");
    }

    protected override void EndPlay()
    {
        Console.WriteLine("Chess: Game finished");
    }
}

public class Football : Game
{
    protected override void Initialize()
    {
        Console.WriteLine("Football: Setting up field and teams");
    }

    protected override void StartPlay()
    {
        Console.WriteLine("Football: Kickoff!");
    }

    protected override void EndPlay()
    {
        Console.WriteLine("Football: Game over");
    }
}

Applications

Enterprise Use Cases:

  1. Framework Design: Defining algorithm structure while allowing customization
  2. Report Generation: Different report formats with same generation steps
  3. Data Processing Pipelines: ETL processes with customizable steps
  4. Build Systems: Build processes with customizable compilation steps
  5. Test Frameworks: Test execution with setup/teardown hooks
  6. Workflow Engines: Workflow steps with customizable implementations
  7. Code Generation: Code generation with template customization
  8. API Request Processing: Request handling with customizable steps

Benefits:

  • Eliminates code duplication
  • Controls algorithm structure in one place
  • Follows Open/Closed Principle—open for extension, closed for modification
  • Enforces algorithm structure
  • Allows subclasses to customize specific steps
  • Promotes code reuse

When to Use:

  • You have an algorithm with invariant and variant parts
  • Multiple classes share the same algorithm structure
  • You want to control algorithm structure in one place
  • Subclasses should customize only specific steps
  • You want to avoid code duplication

Template Method vs Strategy:

  • Template Method: Uses inheritance, defines algorithm structure, subclasses customize steps
  • Strategy: Uses composition, entire algorithm varies, strategies are interchangeable

Hook Methods:

  • Abstract methods: Must be implemented by subclasses
  • Virtual methods: Can be overridden by subclasses (hooks)
  • Concrete methods: Default implementation, rarely overridden

Key Insight: The Template Method pattern defines an algorithm’s structure in a base class method. Specific steps are implemented by subclasses, but the overall algorithm flow remains consistent. This eliminates duplication and ensures all implementations follow the same structure while allowing customization of individual steps.

Leave a Comment