Skip to content

Template method

The Template Method pattern allows you to define the schema of the algorithm. The schema has a set of elements that can be overwritten. These parts should be used to define the algorithm.

The skeleton of such an algorithm is most often defined in the abstract class or in the interface that uses methods defined with the default keyword. It defines several abstract methods that are used in the implementation of the algorithm.

In order to implement such an algorithm, we only need to extend such an abstract class. The task of implementation of such a class is to overwrite only individual steps of the algorithm, i.e. like overwriting abstract methods.

template_method

An Example

The following example implements a performance test schema for a piece of code (although in reality, many additional factors have to be taken into account). It measures the total execution time of a certain number of iterations.

The PerformanceTestTemplate class defines the skeleton of such an algorithm. It has 3 parts to define:

  • the getWarmUpIterationsNum method defines the number of warm-up iterations (i.e. unmeasured)
  • the getIterationsNum method defines the number of measured iterations
  • the iteration method calls the code whose execution time we measure

The algorithm with these parts is defined in the run method. First it runs a certain number of warm-up iterations. Then the proper iterations, the execution time of which is measured in milliseconds. Finally, some statistics are displayed on the screen. Note that the run method is marked as final, ie we cannot change its implementation in the classes that inherit from it.

The RandomListSortingPerformanceTest class extends the PerformanceTestTemplate class. In a single iteration (there are 100 iterations), it creates a list, puts randomly generated numbers into it, and then sorts it. The StringBuilderAppendPerformanceTest class also extends the PerformanceTestTemplate class. In a single operation, it adds one hundred thousand times a randomly generated character to the previously prepared StringBuilder object.

The TemplateMethodUsage class uses the two implementations described above to run the algorithm using therun method.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public abstract class PerformanceTestTemplate {

  protected abstract int getWarmUpIterationsNum();

  protected abstract int getIterationsNum();

  protected abstract void iteration();

  public final void run() {
    for (int warmUpIterationIndex = 0; warmUpIterationIndex < getWarmUpIterationsNum(); warmUpIterationIndex++) {
      iteration();
    }

    final List<Long> iterationsExecutionTimes = new ArrayList<>();

    for (int iterationIndex = 0; iterationIndex < getIterationsNum(); iterationIndex++) {
      long startTimestamp = System.currentTimeMillis();
      iteration();
      long endTimestamp = System.currentTimeMillis();
      iterationsExecutionTimes.add(endTimestamp - startTimestamp);
    }

    showStatistics(iterationsExecutionTimes);
  }

  private void showStatistics(final List<Long> iterationsExecutionTimes) {
    System.out.println("Shortest iteration took " + calculateShortestIteration(iterationsExecutionTimes));
    System.out.println("Longest iteration took " + calculateLongestIteration(iterationsExecutionTimes));
    System.out.println("All iterations took " + calculateTotalExecutionTime(iterationsExecutionTimes));
  }

  private Long calculateShortestIteration(final List<Long> iterationsDurations) {
    return iterationsDurations.stream()
        .min(Comparator.naturalOrder())
        .orElseThrow();
  }

  private Long calculateLongestIteration(final List<Long> iterationsDurations) {
    return iterationsDurations.stream()
        .max(Comparator.naturalOrder())
        .orElseThrow();
  }

  private Long calculateTotalExecutionTime(final List<Long> iterationsDurations) {
    return iterationsDurations.stream().mapToLong(x -> x).sum();
  }
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class RandomListSortingPerformanceTest extends PerformanceTestTemplate {

  private static final int NUMBERS_NUM = 100000;

  @Override
  protected int getWarmUpIterationsNum() {
    return 2;
  }

  @Override
  protected int getIterationsNum() {
    return 100;
  }

  @Override
  protected void iteration() {
    final List<Integer> integers = new ArrayList<>();
    final Random random = new Random();
    for (int idx = 0; idx < NUMBERS_NUM; idx++) {
      integers.add(random.nextInt());
    }

    Collections.sort(integers);
  }
}
import lombok.extern.slf4j.Slf4j;

import java.util.Random;

@Slf4j
public class StringBuilderAppendPerformanceTest extends PerformanceTestTemplate {

  private static final int CHARS_NUM = 1000000;

  @Override
  protected int getWarmUpIterationsNum() {
    return 2;
  }

  @Override
  protected int getIterationsNum() {
    return 100;
  }

  @Override
  protected void iteration() {
    final Random random = new Random();
    final StringBuilder stringBuilder = new StringBuilder();
    for (int idx = 0; idx < CHARS_NUM; idx++) {
      stringBuilder.append(Math.abs(random.nextInt()% 128));
    }
    log.trace(stringBuilder.toString());
  }
}
public class TemplateMethodUsage {
  public static void main(String[] args) {
    PerformanceTestTemplate testTemplate = new RandomListSortingPerformanceTest();
    testTemplate.run();

    testTemplate = new StringBuilderAppendPerformanceTest();
    testTemplate.run();
  }
}

/* Sample output of the program:
Shortest iteration took 17
Longest iteration took 47
All iterations took 1912
Shortest iteration took 15
Longest iteration took 28
All iterations took 1755
*/

Using the Template Method pattern allows you to reduce duplication of code that is written once and resides in the base class. However, the code is closed for algorithm modification, and the potentially large number of abstract steps to define may complicate the implementation.