1. Overview
In this quick tutorial, weβll see how to leverage the template method pattern β one of the most popular GoF patterns.
It makes it easier to implement complex algorithms by encapsulating logic in a single method.
2. Implementation
To demonstrate how the template method pattern works, letβs create a simple example which represents building a computer station.
Given the patternβs definition, the algorithmβs structure will be defined in a base class that defines the template build() method:
public abstract class ComputerBuilder {
// ...
public final Computer buildComputer() {
addMotherboard();
setupMotherboard();
addProcessor();
return new Computer(computerParts);
}
public abstract void addMotherboard();
public abstract void setupMotherboard();
public abstract void addProcessor();
// ...
}
The ComputerBuilder class is responsible for outlining the steps required to build a computer by declaring methods for adding and setting up different components, such as a motherboard and a processor.
Here, the build() method is the template method, which defines steps of the algorithm for assembling the computer parts and returns fully-initialized Computer instances.
Notice that itβs declared as final to prevent it from being overridden.
3. In Action
With the base class already set, letβs try to use it by creating two subclasses. One which builds a βstandardβ computer, and the other that builds a βhigh-endβ computer:
public class StandardComputerBuilder extends ComputerBuilder {
@Override
public void addMotherboard() {
computerParts.put("Motherboard", "Standard Motherboard");
}
@Override
public void setupMotherboard() {
motherboardSetupStatus.add(
"Screwing the standard motherboard to the case.");
motherboardSetupStatus.add(
"Pluging in the power supply connectors.");
motherboardSetupStatus.forEach(
step -> System.out.println(step));
}
@Override
public void addProcessor() {
computerParts.put("Processor", "Standard Processor");
}
}
And hereβs the HighEndComputerBuilder variant:
public class HighEndComputerBuilder extends ComputerBuilder {
@Override
public void addMotherboard() {
computerParts.put("Motherboard", "High-end Motherboard");
}
@Override
public void setupMotherboard() {
motherboardSetupStatus.add(
"Screwing the high-end motherboard to the case.");
motherboardSetupStatus.add(
"Pluging in the power supply connectors.");
motherboardSetupStatus.forEach(
step -> System.out.println(step));
}
@Override
public void addProcessor() {
computerParts.put("Processor", "High-end Processor");
}
}
As we can see, we didnβt need to worry about the whole assembly process but only for providing implementations for separate methods.
Now, letβs see it in action:
new StandardComputerBuilder()
.buildComputer();
.getComputerParts()
.forEach((k, v) -> System.out.println("Part : " + k + " Value : " + v));
new HighEndComputerBuilder()
.buildComputer();
.getComputerParts()
.forEach((k, v) -> System.out.println("Part : " + k + " Value : " + v));
4. Template Methods in Java Core Libraries
This pattern is widely used in the Java core libraries, for example by java.util.AbstractList, or java.util.AbstractSet.
For instance, Abstract List provides a skeletal implementation of the List interface.
An example of a template method can be the addAll() method, although itβs not explicitly defined as final:
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
boolean modified = false;
for (E e : c) {
add(index++, e);
modified = true;
}
return modified;
}
Users only need to implement the add() method:
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
Here, itβs the responsibility of the programmer to provide an implementation for adding an element to the list at the given index (the variant part of the listing algorithm).
5. Conclusion
In this article, we showed the template method pattern and how to implement it in Java.
The template method pattern promotes code reuse and decoupling, but at the expense of using inheritance.
