Template Method Pattern

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses

Understanding the Template Method Pattern

The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class but lets subclasses override specific steps of the algorithm without changing its structure. It enables you to reuse the common parts of an algorithm while allowing customization of the variable parts.

Problem

Imagine you're creating a data mining application that analyzes different document formats (CSV files, PDF documents, Excel spreadsheets). Regardless of the file format, the application needs to perform the same series of steps:

  1. Open the file
  2. Extract the data
  3. Parse the data
  4. Analyze the data
  5. Generate a report
  6. Close the file

The challenge is that some steps (like opening and closing the file) might be common across all file types, while others (like extracting and parsing data) will vary depending on the specific format.

Solution

The Template Method pattern suggests defining a skeleton algorithm in a base class method, called the template method. This method calls a series of other methods, some of which are implemented in the base class (known as "final methods") and others that are left abstract or given a default implementation (known as "hook methods").

Subclasses can then override the hook methods to provide specific implementations for the variable parts of the algorithm, without changing the overall structure defined by the template method.

Structure

Template Method Pattern Structure

Participants

In the Template Method pattern, several types of operations may be called by the template method:

When to Use

Use the Template Method Pattern when:

Benefits

Real-World Uses

Implementation Example

Here's a JavaScript implementation of the Template Method pattern for a data processing system:

// Abstract Class
class DataProcessor {
    // Template Method - defines the algorithm skeleton
    process() {
        this.open();
        this.extract();
        this.transform();
        this.analyze();
        this.report();
        
        // Hook method with default implementation
        if (this.shouldClose()) {
            this.close();
        }
    }
    
    // Final method - same for all subclasses
    open() {
        console.log("Opening data source...");
    }
    
    // Abstract methods - must be implemented by subclasses
    extract() {
        throw new Error("extract() must be implemented by subclass");
    }
    
    transform() {
        throw new Error("transform() must be implemented by subclass");
    }
    
    // Final method - same for all subclasses
    analyze() {
        console.log("Analyzing processed data...");
    }
    
    // Abstract method - must be implemented by subclasses
    report() {
        throw new Error("report() must be implemented by subclass");
    }
    
    // Final method - same for all subclasses
    close() {
        console.log("Closing data source...");
    }
    
    // Hook method with default implementation
    shouldClose() {
        return true; // By default, always close the source
    }
}

// Concrete Class for CSV Files
class CSVProcessor extends DataProcessor {
    extract() {
        console.log("Extracting data from CSV file...");
    }
    
    transform() {
        console.log("Parsing CSV columns and rows...");
    }
    
    report() {
        console.log("Generating CSV report with column statistics...");
    }
}

// Concrete Class for Database
class DatabaseProcessor extends DataProcessor {
    extract() {
        console.log("Executing SQL query on database...");
    }
    
    transform() {
        console.log("Transforming query results into standardized format...");
    }
    
    report() {
        console.log("Generating database query performance report...");
    }
    
    // Override hook method
    shouldClose() {
        console.log("Keeping database connection open for future queries...");
        return false; // Don't close the database connection
    }
}

// Client code
console.log("Processing CSV File:");
const csvProcessor = new CSVProcessor();
csvProcessor.process();

console.log("\nProcessing Database:");
const dbProcessor = new DatabaseProcessor();
dbProcessor.process();

In this example, DataProcessor is the abstract class that defines the template method process(). This method establishes the algorithm's skeleton, calling various steps in a specific order. Some methods are implemented in the base class (like open(), analyze(), and close()), while others are left abstract (like extract(), transform(), and report()).

The CSVProcessor and DatabaseProcessor classes are concrete implementations that provide specific implementations for the abstract methods. Additionally, DatabaseProcessor overrides the hook method shouldClose() to customize the behavior of the algorithm.

Interactive Demo

Experience the Template Method pattern in action with this interactive demo. Select different types of data processors and see how they follow the same algorithm structure while implementing specific steps differently.

Interactive Demo: Data Processing System

Click on a processor type to see how it implements the template method pattern.

Algorithm Flow:

1. open()
2. extract()
3. transform()
4. analyze()
5. report()
6. shouldClose() [Conditional]
7. close() [If shouldClose() returns true]

Execution Output:

Select a processor to see the execution output.