Template Method Pattern

The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a method, allowing subclasses to alter specific steps of the algorithm without changing its structure. This pattern is crucial in situations where the overall algorithm remains consistent, but certain details may vary across different implementations.

Key Components

  1. Abstract Class: Defines the template method (the algorithm’s skeleton) and abstract or hook methods that subclasses can implement or override. The template method is typically final to prevent overriding.
  2. Template Method: A method in the Abstract Class that outlines the algorithm’s steps, calling abstract methods, hook methods, or concrete methods.
  3. Concrete Class: Subclasses that implement the abstract methods or override hook methods to provide specific behavior for the algorithm’s customizable steps.
  4. Client: Uses the Concrete Classes, invoking the template method to execute the algorithm.

How It Works

  • The Abstract Class defines the template method, which provides the fixed structure of the algorithm by calling a sequence of methods (some abstract, some concrete, some optional hooks).
  • Abstract Methods: Must be implemented by subclasses to provide mandatory step-specific behavior.
  • Hook Methods: Optional methods with default (often empty) implementations that subclasses can override for additional customization.
  • Concrete Methods: Common logic implemented in the Abstract Class, used by all subclasses.
  • The Concrete Class implements the abstract methods and optionally overrides hook methods to customize the algorithm.
  • The Client calls the template method on a Concrete Class instance, which executes the algorithm with the customized steps.

Sample Implementaion

A model a meal preparation process where the template method defines the steps to prepare a meal, and subclasses customize specific steps.
// Abstract Class
abstract class MealPreparation {
    // Template Method (final to prevent overriding)
    final void prepareMeal() {
        selectIngredients();
        cook();
        if (shouldAddCondiments()) {
            addCondiments();
        }
        serve();
    }

    // Abstract Methods (must be implemented by subclasses)
    abstract void selectIngredients();
    abstract void cook();

    // Hook Method (optional, default implementation)
    boolean shouldAddCondiments() {
        return true; // Default: condiments are added
    }

    // Hook Method (optional, default empty)
    void addCondiments() {
        System.out.println("Adding default condiments (coriander, ghee).");
    }

    // Concrete Method (shared by all subclasses)
    void serve() {
        System.out.println("Serving the meal with chapati or rice.");
    }
}

// Concrete Class 1
class DalRiceMeal extends MealPreparation {
    @Override
    void selectIngredients() {
        System.out.println("Selecting toor dal, rice, onions, tomatoes, spices.");
    }

    @Override
    void cook() {
        System.out.println("Cooking dal with spices and boiling rice separately.");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding fresh coriander and a dollop of ghee.");
    }
}

// Concrete Class 2
class VegBiryaniMeal extends MealPreparation {
    @Override
    void selectIngredients() {
        System.out.println("Selecting basmati rice, carrots, peas, paneer, biryani masala.");
    }

    @Override
    void cook() {
        System.out.println("Sautéing vegetables and spices, layering with rice, and cooking in a sealed pot.");
    }

    @Override
    boolean shouldAddCondiments() {
        return false; // Skip condiments for biryani (served as is)
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        System.out.println("Preparing Dal Rice Meal:");
        MealPreparation dalRice = new DalRiceMeal();
        dalRice.prepareMeal();

        System.out.println("\nPreparing Veg Biryani Meal:");
        MealPreparation vegBiryani = new VegBiryaniMeal();
        vegBiryani.prepareMeal();
    }
}
/*
Preparing Dal Rice Meal:
Selecting toor dal, rice, onions, tomatoes, spices.
Cooking dal with spices and boiling rice separately.
Adding fresh coriander and a dollop of ghee.
Serving the meal with chapati or rice.

Preparing Veg Biryani Meal:
Selecting basmati rice, carrots, peas, paneer, biryani masala.
Sautéing vegetables and spices, layering with rice, and cooking in a sealed pot.
Serving the meal with chapati or rice.
*/Code language: JavaScript (javascript)

Use case and Implementation

Defining a general process for handling transactions with specific steps that can be customized by different types of transactions.

//TemplateMethodPatternDemo.java
//Abstract Class with Template Method
abstract class Transaction {
    // Template method
    public final void processTransaction() {
        validateTransaction();
        calculateCharges();
        executeTransaction();
        sendNotification();
    }

    protected abstract void validateTransaction();
    protected abstract void calculateCharges();
    protected abstract void executeTransaction();
    
    // Concrete method
    private void sendNotification() {
        System.out.println("Notification sent to the customer.");
    }
}
//Subclass for Bank Transfer Transaction
class BankTransferTransaction extends Transaction {
    @Override
    protected void validateTransaction() {
        System.out.println("Validating bank transfer details.");
    }

    @Override
    protected void calculateCharges() {
        System.out.println("Calculating bank transfer charges.");
    }

    @Override
    protected void executeTransaction() {
        System.out.println("Executing bank transfer.");
    }
}
//Subclass for Credit Card Transaction
class CreditCardTransaction extends Transaction {
    @Override
    protected void validateTransaction() {
        System.out.println("Validating credit card details.");
    }

    @Override
    protected void calculateCharges() {
        System.out.println("Calculating credit card transaction charges.");
    }

    @Override
    protected void executeTransaction() {
        System.out.println("Executing credit card transaction.");
    }
}
//Subclass for Mobile Payment Transaction
class MobilePaymentTransaction extends Transaction {
    @Override
    protected void validateTransaction() {
        System.out.println("Validating mobile payment details.");
    }

    @Override
    protected void calculateCharges() {
        System.out.println("Calculating mobile payment charges.");
    }

    @Override
    protected void executeTransaction() {
        System.out.println("Executing mobile payment.");
    }
}
//Client Code
public class TemplateMethodPatternDemo {
    public static void main(String[] args) {
        System.out.println("Processing Bank Transfer Transaction:");
        Transaction bankTransfer = new BankTransferTransaction();
        bankTransfer.processTransaction();

        System.out.println("\nProcessing Credit Card Transaction:");
        Transaction creditCard = new CreditCardTransaction();
        creditCard.processTransaction();

        System.out.println("\nProcessing Mobile Payment Transaction:");
        Transaction mobilePayment = new MobilePaymentTransaction();
        mobilePayment.processTransaction();
    }
}

/*
C:\Users\AITS_CCF\Desktop\Bhava Advanced Concurrency\JPDP\JPDPLAB>javac TemplateMethodPatternDemo.java

C:\Users\AITS_CCF\Desktop\Bhava Advanced Concurrency\JPDP\JPDPLAB>java TemplateMethodPatternDemo
Processing Bank Transfer Transaction:
Validating bank transfer details.
Calculating bank transfer charges.
Executing bank transfer.
Notification sent to the customer.

Processing Credit Card Transaction:
Validating credit card details.
Calculating credit card transaction charges.
Executing credit card transaction.
Notification sent to the customer.

Processing Mobile Payment Transaction:
Validating mobile payment details.
Calculating mobile payment charges.
Executing mobile payment.
Notification sent to the customer.
*/

Pros

  • Code Reuse: Common algorithm steps are defined once in the Abstract Class.
  • Open/Closed Principle: Subclasses can extend behavior without modifying the template method.
  • Consistency: Ensures all subclasses follow the same algorithm structure.
  • Flexibility: Hook methods allow optional customization without requiring full implementation.
  • Maintainability: Centralizes shared logic, reducing duplication.

Cons

  • Rigidity: The fixed structure of the template method can limit flexibility if subclasses need radically different algorithms.
  • Class Proliferation: Each new variation requires a new subclass, increasing the number of classes.
  • Complexity: Hook methods and abstract methods can make the design harder to understand for complex algorithms.
  • Inheritance Dependency: Relies on inheritance, which can lead to tight coupling compared to composition-based patterns like Strategy.

When to Use

  • When you have a common algorithm with variable steps that need to be customized by subclasses.
  • When you want to enforce a consistent process while allowing specific steps to vary.
  • When you need to reduce code duplication by centralizing shared logic.
  • When you want to provide optional customization points (hooks) for subclasses.

Real-World Example

  • Data Processing Pipelines: A base class defines steps (read, process, write), with subclasses customizing processing (e.g., CSV vs. JSON parsing).
  • Game Frameworks: A Game class defines initialize(), play(), end(), with subclasses implementing game-specific logic.
  • Build Systems: A build process (compile, test, deploy) with customizable compilation or testing steps for different projects.
  • Report Generators: A report generator defines steps (fetch data, format, output), with subclasses customizing formatting (PDF, HTML).

The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a superclass, allowing subclasses to fill in specific steps without changing the algorithm’s structure. It promotes code reuse, inversion of control, and adherence to the DRY (Don’t Repeat Yourself) principle.

This pattern is particularly useful when:

  • You have a set of classes that share a common algorithm but differ in some specific steps.
  • You want to enforce a consistent execution order across different implementations.
  • You want to avoid duplicating code in multiple subclasses.
Scroll to Top