Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

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.

As always, all the code samples shown in this article are available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.