Template Method Design Pattern in Rust

1. Definition

The Template Method Design Pattern defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

2. Problem Statement

Imagine needing to implement several algorithms that have common steps. The challenge is to avoid code duplication while allowing individual steps to vary between the different implementations.

3. Solution

Use the Template Method pattern to move the invariant parts of the algorithm into a base class, while deferring the variant parts to derived subclasses. This way, the overall algorithm structure remains unchanged, while specific steps can be overridden.

4. Real-World Use Cases

1. Cooking recipes: Common steps like gathering ingredients and serving can be standardized, while the actual cooking process can vary.

2. Data parsers: Common steps such as reading and validating data can be the same, while parsing and interpretation can differ.

3. Software build processes: Steps like code checkout and deployment might remain the same, while the compile and link steps could vary.

5. Implementation Steps

1. Define an abstract class with the template method, which outlines the algorithm structure.

2. Implement invariant parts of the algorithm in the base class.

3. Use placeholder methods (typically abstract) in the base class for variant parts.

4. Extend the base class in subclasses and override the placeholder methods.

6. Implementation in Rust Programming

// Base class defining the template method
abstract class Algorithm {
    // Template method
    pub fn execute(&self) {
        self.step_one();
        self.step_two();
        self.step_three();
    }
    fn step_one(&self) {
        println!("Step 1: Common Initialization");
    }
    // Placeholder methods to be overridden in subclasses
    fn step_two(&self);
    fn step_three(&self) {
        println!("Step 3: Common Cleanup");
    }
}
// Concrete implementation
struct ConcreteAlgorithmA;
impl Algorithm for ConcreteAlgorithmA {
    fn step_two(&self) {
        println!("Step 2A: Specific Processing for A");
    }
}
struct ConcreteAlgorithmB;
impl Algorithm for ConcreteAlgorithmB {
    fn step_two(&self) {
        println!("Step 2B: Specific Processing for B");
    }
}
// Client code
fn main() {
    let algo_a = ConcreteAlgorithmA;
    let algo_b = ConcreteAlgorithmB;
    println!("Executing Algorithm A:");
    algo_a.execute();
    println!("\nExecuting Algorithm B:");
    algo_b.execute();
}

Output:

Executing Algorithm A:
Step 1: Common Initialization
Step 2A: Specific Processing for A
Step 3: Common Cleanup
Executing Algorithm B:
Step 1: Common Initialization
Step 2B: Specific Processing for B
Step 3: Common Cleanup

Explanation:

1. The Algorithm trait represents the abstract base class with the template method execute.

2. The common steps (step_one and step_three) are implemented directly in the base trait.

3. The variant step (step_two) is left unimplemented, allowing subclasses to provide their specifics.

4. Two concrete implementations, ConcreteAlgorithmA and ConcreteAlgorithmB, override the step_two method to provide their unique processing.

5. The client code demonstrates the use of both concrete algorithms, which follow the same structure but have different specifics in step_two.

7. When to use?

Use the Template Method pattern when:

1. Common behavior among subclasses should be factored out to avoid code duplication.

2. You want to let subclasses implement parts of an algorithm without changing its overall structure.

3. Subclasses should be able to extend only specific parts of a large algorithm, without altering the rest.


Comments