The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a superclass but lets subclasses override specific steps of the algorithm without changing its structure. This can be useful when you have several subclasses that implement variations of the same algorithm, but most of the algorithm’s logic is common to all subclasses. By using the Template Method pattern, you can minimize code duplication and make it easy to add new variations to the algorithm later on.
Here’s a simple example that demonstrates how you might use the Template Method pattern to implement a simple sorting algorithm that supports multiple variations:
public abstract class SortAlgorithm {
public final void sort(int[] arr) {
// Step 1: Perform any pre-processing required
prepare(arr);
// Step 2: Perform the actual sorting algorithm (implemented in a subclass)
performSort(arr);
// Step 3: Perform any post-processing required
cleanup(arr);
}
protected void prepare(int[] arr) {
// Default implementation performs no pre-processing, but can be overridden by a subclass
}
protected abstract void performSort(int[] arr);
protected void cleanup(int[] arr) {
// Default implementation performs no post-processing, but can be overridden by a subclass
}
}
In this example, the ‘SortAlgorithm‘ class defines the skeleton of a sorting algorithm using the Template Method pattern. The ‘sort()‘ method is the Template Method itself, which calls several other methods to carry out the algorithm:
1. ‘prepare()‘ - a hook method that can be overridden by a subclass to perform any pre-processing required before running the sorting algorithm.
2. ‘performSort()‘ - an abstract method that is implemented by a subclass to perform the actual sorting algorithm. This is the only method that must be implemented by a subclass; the others have default implementations that can be overridden if necessary.
3. ‘cleanup()‘ - a hook method that can be overridden by a subclass to perform any post-processing required after the sorting algorithm has run.
Here’s an example of a subclass that implements a bubble sort algorithm:
public class BubbleSort extends SortAlgorithm {
@Override
protected void performSort(int[] arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 1; j < (arr.length - i); j++) {
if (arr[j - 1] > arr[j]) {
// Swap elements
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
}
}
In this example, the ‘BubbleSort‘ class extends the ‘SortAlgorithm‘ class and implements the ‘performSort()‘ method to implement the bubble sort algorithm. The ‘prepare()‘ and ‘cleanup()‘ methods are not overridden, so they use the default implementations inherited from the ‘SortAlgorithm‘ class.
Here’s an example of how you might use the ‘SortAlgorithm‘ class to run a bubble sort:
int[] arr = {5, 2, 8, 3, 1, 6};
SortAlgorithm sort = new BubbleSort();
sort.sort(arr);
In this example, we create an integer array and a ‘BubbleSort‘ object, and then call the ‘sort()‘ method on the ‘BubbleSort‘ object to run the sorting algorithm. Because the ‘sort()‘ method is defined in the superclass (‘SortAlgorithm‘), we can easily swap out different sorting algorithms (such as quicksort or merge sort) simply by creating a different subclass that overrides the ‘performSort()‘ method. And because the other methods (‘prepare()‘ and ‘cleanup()‘) are hook methods with default implementations, we only need to override them if we need to perform any additional pre- or post-processing specific to the algorithm we’re using.