개발/Architecture & Design Pattern

C++ - 템플릿 메서드 패턴 (Template Method Pattern)

피로물든딸기 2024. 2. 26. 22:00
반응형

C, C++ 전체 링크

Architecture & Design Pattern 전체 링크

 

참고

- 클래스 다이어그램 그리기

- virtual 키워드

헐리우드 원칙

 

템플릿 메서드 패턴 (Template Method Pattern) - 행동 패턴

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

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

 

구현

- Template Method : 알고리즘의 구조를 정의한 메서드, final 등으로 재정의가 불가능하도록 정의

- Abstract Method : Template Method에서 정의한 구조의 일부 단계를 구체화하기 위해 하위 클래스에서 구현

- Hook : 하위 클래스에서 선택적으로 오버라이드 할 수 있도록 제공되는 메서드

 

장점

- 알고리즘 구조를 상위 클래스에 정의하므로, 하위 클래스에서 동일한 알고리즘을 재사용한다.

- 알고리즘의 구조가 명확하게 정의되어 있으므로, 수정이나 변경이 필요할 때 해당 부분만 수정할 수 있다.

- 하위 클래스에서 오버라이딩이 가능하므로 유연성이 높다.

 

단점

- 상속을 사용하기 때문에 하위 클래스가 상위 클래스에 강하게 결합된다.

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

- 템플릿 메서드에서 구성되는 단계가 많을수록 유지 보수가 힘들어진다.


알고리즘 

 

알고리즘 문제를 풀기 위해 input → sort search output 과정을 거쳐야 한다.

그래서 알고리즘 클래스를 아래와 같이 정의하였고, 퀵 정렬 이분 탐색을 사용하였다.

class MyAlgorithm1
{
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; }
};

 

문제를 풀기 위해 알고리즘 전략을 바꿀 수도 있다. 

아래 경우에는 머지 소트선형 탐색을 사용하였다.

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

 

전체 코드는 다음과 같다.

#include <iostream>

using namespace std;

class MyAlgorithm1
{
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; }
};

class MyAlgorithm2
{
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()
{
	MyAlgorithm1* al1 = new MyAlgorithm1();
	MyAlgorithm2* al2 = new MyAlgorithm2();

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

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

	return 0;
}


템플릿 메서드 패턴 적용

 

MyAlgorithm1, 2 모두 run 메서드 내에서 동일하게  input → sort  search  output 과정을 거친다.

sortsearch는 알고리즘마다 다르므로 virtual로 선언하고,

템플릿 메서드(= run)에서 위의 과정(알고리즘 구조)추상화가 가능하다.

 

sortsearch만 각 알고리즘에서 구체적으로 구현할 수 있도록 virtual로 선언한다.

템플릿 메서드알고리즘 구조를 유지하기 때문에 final 키워드확장이 불가능하도록 한다.

 

템플릿 메서드를 적용한 코드는 다음과 같다.

#include <iostream>

using namespace std;

class AlgorithmTemplate
{
public:
	virtual ~AlgorithmTemplate() = default;
	virtual void run() final // template method 는 재정의 불가
	{
		input();
		sort();
		search();
		output();
	}

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

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

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

int main()
{
	MyAlgorithm1* al1 = new MyAlgorithm1();
	MyAlgorithm2* al2 = new MyAlgorithm2();

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

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

	return 0;
}


Hook

 

템플릿 메서드를 가지고 있는 추상 클래스는 기본적인 메서드(input, output)

서브 클래스에서 구현해야 하는 메서드(sort, search)로 나뉜다.

여기에 구상 메서드 hook()을 추가하여 알고리즘 구조를 변경할 수 있다.

여기서 templateMethod고수준 구성요소이고, 추상화를 통해 저수준 구성요소를 호출한다. (헐리우드 원칙)

 

예를 들어 checkSort(= hook)false인 경우에 sort를 사용하지 않도록 구조를 변경해 보자.

class AlgorithmTemplate
{
public:
	virtual ~AlgorithmTemplate() = default;
	virtual void run() final // template method 는 재정의 불가
	{
		input();
		if (checkSort()) sort();
		search();
		output();
	}

protected:
	virtual bool checkSort() { return true; }
	...
};

 

예를 들어 MyAlgorithm2선형 탐색을 하기 때문에 정렬을 할 필요가 없다.

따라서 checkSortfalse로 재정의하여 다음과 같이 알고리즘 과정에서 sort를 제거할 수 있다.

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

 

전체 코드는 다음과 같다.

#include <iostream>

using namespace std;

class AlgorithmTemplate
{
public:
	virtual ~AlgorithmTemplate() = default;
	virtual void run() final // template method 는 재정의 불가
	{
		input();
		if (checkSort()) sort();
		search();
		output();
	}

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

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

class MyAlgorithm2 : 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()
{
	MyAlgorithm1* al1 = new MyAlgorithm1();
	MyAlgorithm2* al2 = new MyAlgorithm2();

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

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

	return 0;
}

 

Algorithm2에서 sort실행되지 않은 것을 알 수 있다.


클래스 정렬하기

 

정렬 또한 일종의 템플릿 메서드 패턴으로 볼 수 있다.

정렬은 각 객체를 비교(compare = hook)하고, 비교 조건에 따라 swap하기 때문이다.

 

상품 객체는 상품의 이름가격을 멤버로 가지고 있다.

class Product
{
public:
	Product(string n, int p) : name(n), price(p) { }
	int getPrice() { return price; }
	string getName() { return name; }

private:
	string name;
	int price;
};

 

상품의 목록을 생성해서 정렬해 보자.

	vector<Product> products = {
		Product("p1", 3000),
		Product("p2", 2000),
		Product("p3", 1000),
		Product("p4", 7000),
		Product("p5", 9000),
	};

 

상품 정렬해도 컴파일 에러가 발생한다.

어떤 상품 클래스가 작고 큰지 알 수 없기 때문이다.

sort(products.begin(), products.end());

 

따라서 상품에 대한 비교 연산 operator를 재정의 해야 한다. 

아래는 가격이 작은 순으로 정렬되도록 재정의하였다.

bool operator<(Product& lhs, Product& rhs)
{
	return lhs.getPrice() < rhs.getPrice();
}

 

또는 compare 함수를 직접 만들어서 sort에 추가해도 된다. 

compare 함수는 가격이 큰 순으로 정렬되도록 하였다.

bool compare(Product& lhs, Product& rhs) 
{
	return lhs.getPrice() > rhs.getPrice();
}

int main()
{
	...
    
	sort(products.begin(), products.end(), compare);

	return 0;
}

 

아래 코드를 실행하면 정렬이 정상적으로 이루어지는 것을 알 수 있다.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

class Product
{
public:
	Product(string n, int p) : name(n), price(p) { }
	int getPrice() { return price; }
	string getName() { return name; }

private:
	string name;
	int price;
};

bool operator<(Product& lhs, Product& rhs)
{
	return lhs.getPrice() < rhs.getPrice();
}

bool compare(Product& lhs, Product& rhs)
{
	return lhs.getPrice() > rhs.getPrice();
}

void display(const vector<Product>& products)
{
	for (auto p : products)
		cout << p.getName() << "] price : " << p.getPrice() << endl;
}

int main()
{
	vector<Product> products = {
		Product("p1", 3000),
		Product("p2", 2000),
		Product("p3", 1000),
		Product("p4", 7000),
		Product("p5", 9000),
	};

	display(products); cout << endl;

	sort(products.begin(), products.end());
	display(products); cout << endl;

	sort(products.begin(), products.end(), compare);
	// or lambda
	// sort(products.begin(), products.end(), 
	// 	[](Product &lhs, Product &rhs) { return lhs.getPrice() > rhs.getPrice(); });
	display(products); cout << endl;

	return 0;
}


ClassDiagram.md

```mermaid
  classDiagram    
    class AlgorithmTemplate{
      run()
      input()
      sort()*
      search()*
      output()
    }    
    class ConcreteAlgorithmA {
      sort()
      search()
    }
    class ConcreteAlgorithmB {
      sort()
      search()
    }

    AlgorithmTemplate <|-- ConcreteAlgorithmA
    AlgorithmTemplate <|-- ConcreteAlgorithmB        
```

```mermaid
  classDiagram    
    class AbstractClass {
      templateMethod()
      primitiveStep1()
      hook()
      concreteStep2()* 
      concreteStep3()* 
      primitiveStep4()
    }    

    class ConcreteClassA {
      concreteStep2()
      concreteStep3()
    }
    class ConcreteClassB {
      concreteStep2()
      concreteStep3()
    }

    AbstractClass <|-- ConcreteClassA
    AbstractClass <|-- ConcreteClassB    

    note for anything "if(hook() == true) concreteStep2();"     
```
반응형