템플릿 메서드 패턴
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 99번째.
템플릿 메서드 패턴(Template Method pattern)은 알고리즘의 골격을 기본 클래스에서 정의하여, 알고리즘의 전체적인 구조를 변경하지 않고도 서브클래스에서 특정 단계를 재정의할 수 있도록 합니다. 전체 알고리즘을 교체하는 전략(Strategy) 패턴과 달리, 템플릿 메서드는 알고리즘을 고정된 상태로 유지하면서 개별 단계의 커스터마이징을 허용합니다.
기본 클래스는 템플릿 메서드(알고리즘)를 구현하고, 하위 클래스가 반드시 제공해야 하는 추상 또는 가상 메서드를 호출합니다:
#include <iostream>
class DataProcessor {
public:
// 템플릿 메서드 - 알고리즘의 골격을 정의합니다
void process() {
loadData();
processData();
saveResults();
}
virtual ~DataProcessor() = default;
protected:
virtual void loadData() = 0; // 반드시 구현되어야 함
virtual void processData() = 0; // 반드시 구현되어야 함
// 훅(Hook) - 기본 동작을 포함하며 선택적으로 오버라이드 가능
virtual void saveResults() {
std::cout << "Saving to default location\n";
}
};
class CSVProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading CSV file\n";
}
void processData() override {
std::cout << "Parsing CSV data\n";
}
};
class JSONProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading JSON file\n";
}
void processData() override {
std::cout << "Parsing JSON data\n";
}
void saveResults() override {
std::cout << "Saving to cloud storage\n";
}
};
int main() {
CSVProcessor csv;
csv.process(); // 기본 saveResults를 사용함
JSONProcessor json;
json.process(); // 사용자 정의 saveResults를 사용함
}process() 메서드는 템플릿 메서드입니다. 이는 고정된 단계의 순서를 정의합니다. 서브클래스는 loadData()와 processData()를 서로 다르게 구현하지만, 그 순서는 절대 변하지 않습니다. saveResults() 메서드는 "훅(hook)"으로, 서브클래스에서 선택적으로 오버라이드할 수 있는 기본 구현을 제공합니다.
알고리즘의 단계는 고정되어 있지만 특정 작업에서 유연성이 필요할 때, 또는 유사한 클래스들 간의 코드 중복을 피하고 싶을 때 템플릿 메서드(Template Method) 패턴을 사용하세요.
챌린지
쉬움템플릿 메서드(Template Method) 패턴을 사용하여 보고서 생성기(Report Generator) 시스템을 구축해 보겠습니다. 서로 다른 유형의 보고서(예: 매출 보고서 및 재고 보고서)가 동일한 생성 프로세스를 따르되, 각 보고서 유형이 특정 단계를 사용자 정의하는 프레임워크를 만들 것입니다. 이는 전체 워크플로는 고정되어 있고 개별 단계가 가변적인 템플릿 메서드 패턴을 적용하기에 완벽한 시나리오입니다.
코드는 다음 세 개의 파일로 구성됩니다:
ReportGenerator.h: 템플릿 메서드가 포함된 추상 기본 클래스를 정의합니다.보고서 생성 알고리즘의 골격을 정의하는
ReportGenerator클래스를 만듭니다. 템플릿 메서드인generateReport()는 다음 단계들을 순서대로 실행해야 합니다:gatherData()— 순수 가상 함수로, 하위 클래스에서 반드시 구현해야 합니다.formatReport()— 순수 가상 함수로, 하위 클래스에서 반드시 구현해야 합니다.addHeader()—--- Report ---를 출력하는 기본 동작을 가진 훅(hook) 메서드입니다.printReport()— 순수 가상 함수로, 하위 클래스에서 반드시 구현해야 합니다.
addHeader()메서드는 하위 클래스에서 헤더를 사용자 정의하기 위해 선택적으로 오버라이드할 수 있는 훅 역할을 합니다.Reports.h: 두 개의 구체적인 보고서 생성기를 구현합니다.다음과 같은
SalesReport클래스를 만듭니다:gatherData()는Gathering sales data from database를 출력합니다.formatReport()는Formatting sales figures를 출력합니다.printReport()는Sales Total: $[amount]를 출력하며, 여기서 amount는 생성자로 전달된 값입니다.
다음과 같은
InventoryReport클래스를 만듭니다:gatherData()는Scanning inventory records를 출력합니다.formatReport()는Organizing inventory by category를 출력합니다.addHeader()는 훅을 오버라이드하여=== Inventory Report ===를 출력합니다.printReport()는Items in stock: [count]를 출력하며, 여기서 count는 생성자로 전달된 값입니다.
두 클래스 모두 생성자를 통해 각각의 값(amount 또는 count)을 받아야 합니다.
main.cpp: 템플릿 메서드 패턴을 시연합니다.두 개의 입력을 읽습니다:
- 매출 금액 (정수)
- 재고 수량 (정수)
매출 금액으로
SalesReport를 생성하고 실행합니다. 그런 다음 재고 수량으로InventoryReport를 생성하고 실행합니다. 가독성을 위해 두 보고서 사이에 빈 줄을 출력합니다.
예를 들어, 입력값이 15000과 250인 경우:
Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $15000
Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 250입력값이 8500과 120인 경우:
Gathering sales data from database
Formatting sales figures
--- Report ---
Sales Total: $8500
Scanning inventory records
Organizing inventory by category
=== Inventory Report ===
Items in stock: 120두 보고서 모두 generateReport()에 정의된 정확히 동일한 단계 시퀀스를 따르지만, 각 보고서가 해당 단계들을 다르게 구현하는 방식에 주목하십시오. 또한 InventoryReport는 헤더를 사용자 정의하기 위해 훅 메서드를 오버라이드하는 것을 보여주는 반면, SalesReport는 기본값을 사용합니다. 이것이 바로 템플릿 메서드 패턴의 실제 작동 방식입니다. 알고리즘 구조는 기본 클래스에 고정되어 있지만 세부 사항은 유연하게 변경할 수 있습니다.
치트 시트
템플릿 메서드 패턴(Template Method pattern)은 알고리즘의 골격을 기본 클래스에서 정의하고, 알고리즘의 전체적인 구조를 변경하지 않고도 서브클래스에서 특정 단계를 재정의할 수 있도록 합니다.
기본 클래스는 템플릿 메서드(알고리즘)를 구현하고 서브클래스가 반드시 제공해야 하는 추상 또는 가상 메서드를 호출합니다:
#include <iostream>
class DataProcessor {
public:
// 템플릿 메서드 - 알고리즘 골격을 정의합니다
void process() {
loadData();
processData();
saveResults();
}
virtual ~DataProcessor() = default;
protected:
virtual void loadData() = 0; // 반드시 구현되어야 함
virtual void processData() = 0; // 반드시 구현되어야 함
// 훅(Hook) - 기본 동작을 포함하며 선택적으로 재정의 가능
virtual void saveResults() {
std::cout << "Saving to default location\n";
}
};
class CSVProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading CSV file\n";
}
void processData() override {
std::cout << "Parsing CSV data\n";
}
};
class JSONProcessor : public DataProcessor {
protected:
void loadData() override {
std::cout << "Loading JSON file\n";
}
void processData() override {
std::cout << "Parsing JSON data\n";
}
void saveResults() override {
std::cout << "Saving to cloud storage\n";
}
};템플릿 메서드는 고정된 단계의 순서를 정의합니다. 서브클래스는 추상 메서드를 서로 다르게 구현하지만, 그 순서는 절대 변하지 않습니다. 훅(Hook) 메서드는 서브클래스가 선택적으로 재정의할 수 있는 기본 구현을 가집니다.
변하지 않는 단계가 포함된 알고리즘이 있지만 특정 작업에 유연성이 필요한 경우, 또는 유사한 클래스들 사이의 코드 중복을 피하고 싶은 경우에 템플릿 메서드를 사용하세요.
직접 해보기
#include <iostream>
#include "Reports.h"
using namespace std;
int main() {
// 입력 읽기
int salesAmount;
int inventoryCount;
cin >> salesAmount;
cin >> inventoryCount;
// TODO: salesAmount를 사용하여 SalesReport를 생성하고 실행하세요
// TODO: 보고서 사이에 빈 줄을 출력하세요
// TODO: inventoryCount를 사용하여 InventoryReport를 생성하고 실행하세요
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.