템플릿 매서드 패턴이란?

 

알고리즘의 구조를 정의하는 패턴

알고리즘의 구조는 유지하면서 알고리즘의 일부 단계를 서브클래스에서 구현

 

팩토리 메서드 패턴에서 생성자 내부에 들어있는 함수들의 형태와 같다.

 

 

 

템플릿 메서드 패턴의 구조

 

 

1) AbstractClass

- templateMethod() : 알고리즘의 구조를 정의한 메서드, final로 재정의 불가능하게 작성하며 알고리즘이 작동해야할 순서대로 함수들을 나열해둔 퍼사드이다.

- hook1, hook2 : templateMethod()에 들어있는 퍼사드의 부품들이다.

 

함수1 -> 함수2 -> 함수3 -> 함수4 로 구현된 templatMethod()가 있을 때, 함수1과 함수4는 언제나 동일하지만 함수2와 함수3이 상황에 따라 변한다면 함수2, 함수3을 hook으로 추상적이게 구현하고 하위클래스에서 구체화한다.

 

방법으로는 각 클래스마다 사용할 방법을 구체화해서 구현하면 되고

만약 아예 해당 함수를 off하고 싶다면 hook개념을 이용한다.(물론 오버라이딩하고 함수내용을 비워도 되겠지만)

hook을 사용하는 방법은 AbstractClass에 checkFunc2()를 만들고 return true;를 작성한다.

그리고 if(checkFunc2()) 함수2(); 로 구현하면 하위클래스에서 checkFunc2()를 return false;로 오버라이딩 하지 않으면 함수2가 실행되며, false로 오버라이딩했다면 함수2가 실행되지 않는다.

 

 

 

템플릿 메서드 패턴이 없다면

 

알고리즘의 과정이 input -> sort -> search -> output순으로 진행된다면

퍼사드로 run함수를 만들고 내부에서 위의 4개의 함수를 순차적으로 호출하면 된다.

#include <iostream>
using namespace std;

/*
Algorithm1의 경우 퀵소트와 이분탐색을 사용한다.
*/
class Algorithm1
{
public:
	void run()
	{
		input();
		quickSort();
		binarySearch();
		output();
	}

private:
	void input() { cout << "Input" << endl; }
	void quickSort() { cout << "Quick Sort" << endl; }
	void binarySearch() { cout << "Binary Search" << endl; }
	void output() { cout << "Output" << endl; }
};

/*
Algorithm2의 경우 병합정렬과 선형탐색을 사용한다.
*/
class Algorithm2
{
public:
	void run()
	{
		input();
		mergeSort();
		linearSearch();
		output();
	}

private:
	void input() { cout << "Input" << endl; }
	void mergeSort() { cout << "Merge Sort" << endl; }
	void linearSearch() { cout << "Linear Search" << endl; }
	void output() { cout << "Output" << endl; }
};

int main()
{
	Algorithm1* al1 = new Algorithm1();
	Algorithm2* al2 = new Algorithm2();

	cout << "Algorithm 1" << endl;
	al1->run();

	cout << "\nAlgorithm 2" << endl;
	al2->run();

	return 0;
}

서로 다른 알고리즘을 위해 2개의 클래스를 만들고 각각 구현하는 방식이다.

코드의 중복이 많고 알고리즘 진행 순서가 변경된다면 모든 클래스를 다 변경해줘야 하는 문제가 생길 수 있다.

 

 

 

템플릿 메서드 패턴을 사용한다면

 

상위클래스를 만들어주고 공통된 부분은 상위클래스에서 작성하며, 알고리즘마다 다를 부분은 virtual로 구현해서 하위클래스에서 구체화한다.

이를통해 공통된 부분을 여러번 작성하지 않아도 되므로 코드의 중복이 줄어든다.

 

또한 run()함수가 변경되면 모든 클래스를 다 찾아다니며 수정할 필요 없이 상위클래스 1개만 변경해주면 된다.

또한 알고리즘의 구조인 templateMethod인 run()함수를 변경할 수 없도록 final로 구현한다.

#include <iostream>
using namespace std;

class AlgorithmTemplate
{
public:
	virtual ~AlgorithmTemplate() = default;
	virtual void run() final // template method는 오버라이딩을 막기 위해 final로 구현
	{
		input();
		sort();
		search();
		output();
	}

protected:
	void input() { cout << "Input" << endl; }
	virtual void sort() = 0;
	virtual void search() = 0;
	void output() { cout << "Output" << endl; }
};

class Algorithm1 : public AlgorithmTemplate
{
public:
	void sort() override { cout << "Quick Sort" << endl; }
	void search() override { cout << "Binary Search" << endl; }
};

class Algorithm2 : public AlgorithmTemplate
{
public:
	void sort() override { cout << "Merge Sort" << endl; }
	void search() override { cout << "Linear Search" << endl; }
};

int main()
{
	Algorithm1* al1 = new Algorithm1();
	Algorithm2* al2 = new Algorithm2();

	cout << "Algorithm 1" << endl;
	al1->run();

	cout << "\nAlgorithm 2" << endl;
	al2->run();

	return 0;
}

 

팩토리 메서드 패턴의 생성자 내에서의 코드와 거의 유사하다.

 

 

 

Hook으로 함수 사용유무 결정

 

1) 중간에 들어있는 함수의 실행 여부 결정

위의 코드 중 Algorithm2는 선형탐색을 하므로 sort가 필요없다.

그러므로 checkSort함수(Hook)를 만들어서 sort를 사용하지 않게 할 수 있다.

#include <iostream>
using namespace std;

class AlgorithmTemplate
{
public:
	virtual ~AlgorithmTemplate() = default;
	virtual void run() final // template method는 오버라이딩을 막기 위해 final로 구현
	{
		input();
		if (checkSort()) sort();
		search();
		output();
	}

protected:
	virtual bool checkSort() { return true; }
	void input() { cout << "Input" << endl; }
	virtual void sort() = 0;
	virtual void search() = 0;
	void output() { cout << "Output" << endl; }
};

class Algorithm1 : public AlgorithmTemplate
{
public:
	void sort() override { cout << "Quick Sort" << endl; }
	void search() override { cout << "Binary Search" << endl; }
};

class Algorithm2 : public AlgorithmTemplate
{
public:
	bool checkSort() override { return false; } // hook
	void sort() override { cout << "Merge Sort" << endl; }
	void search() override { cout << "Linear Search" << endl; }
};

int main()
{
	Algorithm1* al1 = new Algorithm1();
	Algorithm2* al2 = new Algorithm2();

	cout << "Algorithm 1" << endl;
	al1->run();

	cout << "\nAlgorithm 2" << endl;
	al2->run();

	return 0;
}

 

위처럼 Hook역할을 하는 checkSort()함수를 만들어서 사용하지 않을 곳에서만 checkSort()를 오버라이딩해서 false로 변경하면 된다.

물론 그냥 오버라이딩하고 함수 내용을 비워버리는것도 가능하겠지만 그러면 함수를 작성중인건지, 실수로 지운건지 등등 의도를 알기 힘드므로 Hook을 쓰는쪽이 의도가 더 명확하게 보이는것같긴 하다.

아래의 코드 방식은 함수를 그냥 비워버리는 방식으로 사용하는 예시이다.

 

2) templateMethod()의 전/후에 작업이 필요할수도/없을수도 있는 경우

예를들어 위의 코드처럼 input -> sort -> search -> output으로만 진행되는것이 확정적이라면 문제가 없지만

특정 알고리즘에서는 input -> sort -> search -> output -> save로 마지막에 저장하는 함수가 추가로 필요할 수도 있다.

 

이 경우도 위처럼 checkSave()함수를 Hook으로 만들어서 실행여부를 정할 수도 있지만 아래처럼 구현하는것도 가능하다.

#include <iostream>
using namespace std;

class AlgorithmTemplate
{
public:
	virtual ~AlgorithmTemplate() = default;
	virtual void run() final // template method는 오버라이딩을 막기 위해 final로 구현
	{
		input();
		sort();
		search();
		output();
		save();
	}

protected:	
	void input() { cout << "Input" << endl; }
	virtual void sort() = 0;
	virtual void search() = 0;
	void output() { cout << "Output" << endl; }
	virtual void save() {} //hook함수를 구현하지 않고 빈상태로 둔다.
};

class Algorithm3 : public AlgorithmTemplate
{
public:
	void sort() override { cout << "Merge Sort" << endl; }
	void search() override { cout << "Linear Search" << endl; }
	void save() override { cout << "Save" << endl; }
};

결국 상위클래스에서 알고리즘의 구조를 정의하고 하위클래스에서 구체화한다는 면에서 이런 구현도 템플릿 메서드 패턴이라고 볼 수 있다.

 

 

 

장단점

 

1) 장점

- 알고리즘의 구조가 상위클래스에서 정의되므로 하위 클래스에서는 동일한 코드를 반복하지 않아도 된다.

- 알고리즘의 구조는 명확하게 정의되어 있으므로 변경이 필요한 경우 해당 부분만 변경하면 된다.

 

2) 단점

- 불필요한 메서드도 강제로 구현해야 하는 경우가 생길 수 있다.

- templateMethod()내의 함수가 많고, 경우의 수가 많다면 구현해야할 클래스가 많아지므로 유지보수가 힘들어 질 수 있다.

 

 

 

 

 

 

※ 참고 문헌

https://d-yong.tistory.com/41

https://copynull.tistory.com/124

https://bloodstrawberry.tistory.com/1395