Template Method Design Pattern in Go

1. Definition

The Template Method Design Pattern defines the structure of an algorithm in a method of a base class but delays some of the steps to subclasses. This allows subclasses to redefine certain steps of the algorithm without changing the algorithm’s structure.

2. Problem Statement

Imagine having to create multiple classes that have similar behaviors, with some steps being identical and some varying. Duplicating the code for the common steps in each class leads to redundancy, while also making it challenging to ensure consistent behavior across all implementations.

3. Solution

Define the steps of the algorithm in the base class and make the varying steps abstract or overridable. Subclasses can then provide the implementation for the abstract or overridable steps without changing the overall algorithm.

4. Real-World Use Cases

1. A generic software build process where some steps (like code compilation) are consistent, but others (like pre-build validation or post-build deployment) might vary.

2. Cooking recipes where the overall structure is the same, but some steps (like adding specific ingredients) might differ.

5. Implementation Steps

1. Create an abstract class with a method that outlines the algorithm steps.

2. Make the steps that will vary either abstract or overridable.

3. Create concrete subclasses that implement the variable steps.

6. Implementation in Go

// Abstract class
type AbstractCook struct {}
func (a *AbstractCook) PrepareDish() {
	a.boilWater()
	a.addMainIngredient()
	a.cook()
	a.addCondiments()
}
func (a *AbstractCook) boilWater() {
	fmt.Println("Boiling water...")
}
func (a *AbstractCook) cook() {
	fmt.Println("Cooking...")
}
// These methods will be overridden by concrete classes
func (a *AbstractCook) addMainIngredient() {}
func (a *AbstractCook) addCondiments() {}
// Concrete class: Pasta Cook
type PastaCook struct {
	AbstractCook
}
func (p *PastaCook) addMainIngredient() {
	fmt.Println("Adding pasta...")
}
func (p *PastaCook) addCondiments() {
	fmt.Println("Adding sauce and cheese...")
}
// Concrete class: Rice Cook
type RiceCook struct {
	AbstractCook
}
func (r *RiceCook) addMainIngredient() {
	fmt.Println("Adding rice...")
}
func (r *RiceCook) addCondiments() {
	fmt.Println("Adding salt and butter...")
}
// Client code
func main() {
	pasta := &PastaCook{}
	rice := &RiceCook{}
	fmt.Println("Making Pasta:")
	pasta.PrepareDish()
	fmt.Println("\nMaking Rice:")
	rice.PrepareDish()
}

Output:

Making Pasta:
Boiling water...
Adding pasta...
Cooking...
Adding sauce and cheese...
Making Rice:
Boiling water...
Adding rice...
Cooking...
Adding salt and butter...

Explanation:

1. The AbstractCook defines the algorithm in the PrepareDish method.

2. Common steps like boilWater and cook are implemented directly in the base class.

3. Variable steps like addMainIngredient and addCondiments are left to be overridden by concrete subclasses.

4. Concrete classes PastaCook and RiceCook provide specific implementations for the variable steps.

7. When to use?

Use the Template Method pattern when:

1. You have an algorithm with steps that are mostly the same, but some need to vary depending on the context.

2. Code duplication across classes with similar behavior needs to be minimized.

3. You want to provide a structure for the algorithm, ensuring that certain steps are executed in a particular order.


Comments