Blog Datasheets Home About me Clients My work Services Contact

G2Labs Grzegorz Grzęda

Template Method design pattern

May 18, 2023

Suppose that you want to create an architecture for your simple computing software. For each case you have to input some integers and perform some calculations.

Use case: The user inputs some integers, and as a result, the software computes variations of calculus computations (all of the numbers are summed up to a single result value). Depending on the algorithm, each number may be prepared for calculations, as well as the end result.

This means, that for every algorithm there is the ‘summing’ part, common for every one of them. We know this - we can delegate this functionality to a super class. The second part is the computation structure. First we have to condition the input values. After the summing we have to condition the end result.

Those two conditioning processes are different for each algorithm. They will be implemented in the subclasses.

Example project

This project can be found on my GitHub page. It has the following structure:

1
2
3
4
5
6
template-method-pattern-example-cpp \
	main.cpp
	Makefile
	AComputer.hpp
	SumOfSquares.hpp
	RootsSummed.hpp

The main.cpp and Makefile are self explanatory (I hope). If not, you can visit my other Design Pattern posts and dive deeper.

The AComputer is the abstract superclass, holding the algorithm, memory management and all the mechanisms needed to perform the task.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#ifndef ACOMPUTER_HPP__
#define ACOMPUTER_HPP__

struct AComputer
{
	AComputer(int siz) : size(siz), cnt(0), result(0)
	{
		numbers = new int[siz];
	}
	virtual ~AComputer()
	{
		delete[] numbers;
	}
	void put(int number)
	{
		if (cnt < size)
			numbers[cnt++] = number;
	}
	void compute()
	{
		prepare();
		calculate();
		finalize();
	}
	int getResult()
	{
		return result;
	}

protected:
	int *numbers;
	int size;
	int cnt;
	int result;

	virtual void prepare() = 0;
	virtual void finalize() = 0;

private:
	void calculate()
	{
		result = 0;

		for (int i = 0; i < cnt; i++)
			result += numbers[i];
	}
};

#endif

Here you see the prepare() and finalize() template methods to be implemented in the subclasses, holding specific details about the algorithms.

There are two: SumOfSquares and RootsSummed. The first one:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef SUMOFSQUARES_HPP__
#define SUMOFSQUARES_HPP__

#include "AComputer.hpp"

struct SumOfSquares : AComputer
{
	SumOfSquares(int size) : AComputer(size) {}

private:
	void prepare()
	{
		for (int i = 0; i < cnt; i++)
			numbers[i] *= numbers[i];
	}
	void finalize()
	{
	}
};

#endif // !SUMOFSQUARES_HPP__

Simply squares the input data. The summing is done in the superclass.

The RootsSummed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef ROOTSSUMMED_HPP__
#define ROOTSSUMMED_HPP__

#include "AComputer.hpp"
#include <cmath>

struct RootsSummed : AComputer
{
	RootsSummed(int size) : AComputer(size) {}

private:
	void prepare()
	{
		for (auto i = 0; i < cnt; i++)
			numbers[i] = static_cast<int>(std::sqrt(numbers[i]));
	}
	void finalize()
	{
		result = static_cast<int>(sqrt(result));
	}
};

#endif // !ROOTSSUMMED_HPP

Takes the square root of each input value, gets summed in the superclass and takes the square root of the result. Just a fancy algorithm.

Finally, the main.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <cstdlib>
#include "SumOfSquares.hpp"
#include "RootsSummed.hpp"

int main(int argc, char *argv[])
{
	if (argc < 2)
		return -1;

	auto size = argc - 1;
	auto nums = new int[size];

	for (auto i = 0; i < size; i++)
		nums[i] = std::atoi(argv[i + 1]);

	AComputer *sosq = new SumOfSquares(size);
	AComputer *rtsm = new RootsSummed(size);

	for (auto i = 0; i < size; i++)
	{
		sosq->put(nums[i]);
		rtsm->put(nums[i]);
	}

	sosq->compute();
	rtsm->compute();

	std::cout << "SumOfSquares: " << sosq->getResult() << std::endl;
	std::cout << " RootsSummed: " << rtsm->getResult() << std::endl;

	delete rtsm;
	delete sosq;

	return 0;
}

Here the program takes the input values (if supplied), initiates the two algorithms and performs computations. Lastly, the results are printed. Notice, that sosq and rtsm are both instances of AComputer. We have access only to put(int), compute() and getResults(). The specialized methods prepare() and finalize() are obscured. The AComputer handles all the interactions and sequencing needed.

The example run:

1
2
3
>template-method-pattern-example-cpp.exe 1 2 3 4 5
SumOfSquares: 55
 RootsSummed: 2

You can see, that the Template Method is actually the essence of inheritance. But, here it is put to work in a specific way. The methods need to be called in the superclass in the same order each time - the algorithm method. The subclasses just provide the lacking functionality, the superclass didn’t have. This scaffold lets us create as many algorithms as needed. We could place the algorithms in a collection and perform computation sequentially.

Benefits

We get:

Traps

We have to watch out:

Conclusion

Don’t underestimate the power of the Template Method Design Pattern! Such a simple solution may save you from the hassle of dealing with many detached classes, with undisclosed similarities.


➡️ Visitor design pattern


⬅️ Refactoring legacy code to adhere to SOLID principles


Go back to Posts.