반응형
전략 패턴 (Strategy Pattern) 이란?
전략(Strategy)은 코드 내부에서 로직(Logic)을 처리하는 「알고리즘」
어떤 목적 달성을 위한 수행 방식이라고 생각하면 좋다.
영화관에서 이벤트 영화 예매 방식을 새롭게 정하고자 한다.
선입선출 / 예약제 / 랜덤 배정 등
다양한 "전략(방식)"으로 관람객을 선정할 수 있다.
이러한 전략은 기존에는 없던 새로운 전략이 생길 수도 있고
프로그램 목적에 따라서는 런타임 시간에도 계속 업데이트시 활용될 수 있다.
[예제 코드]
#include <iostream>
#include <vector>
// 전략 인터페이스 (추상 클래스)
class Strategy {
public:
virtual void execute() = 0;
};
// 구체적인 전략 클래스
class ConcreteStrategyA : public Strategy {
public:
void execute() override {
std::cout << "전략 A" << std::endl;
}
};
class ConcreteStrategyB : public Strategy {
public:
void execute() override {
std::cout << "전략 B" << std::endl;
}
};
// 컨텍스트 클래스
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* strategy) : strategy(strategy) {}
void setStrategy(Strategy* newStrategy) {
strategy = newStrategy;
}
void executeStrategy() {
strategy->execute();
}
};
void main() {
ConcreteStrategyA strategyA;
Context context(&strategyA);
context.executeStrategy();
ConcreteStrategyB strategyB;
context.setStrategy(&strategyB);
context.executeStrategy();
}
• Strategy 추상 클래스로 인터페이스 구현
• 각 전략 클래스(ConcreteStrategyA, ConcreteStrategyB) 구현
• Context 클래스는 전략 실행하는 역할
• setStrategy 함수로 런타임에 전략 교체 가능
→ 필요한 "전략"을 캡슐화 하여 교체 가능하다.
이는 코드 확장성 측면에서도 유리하다!
[예제 코드]
정렬 방식을 퀵(Quick), 힙(Heap), 합병(Merge) 등
목적에 맞는 알고리즘 선택이 가능하다.
#include <iostream>
#include <vector>
#include <algorithm>
class SortingStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
};
// Quick Sort
class QuickSort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
std::cout << "Quick Sort" << std::endl;
std::sort(data.begin(), data.end());
}
};
// Heap Sort
class HeapSort : public SortingStrategy {
public:
void sort(std::vector<int>& data) override {
std::cout << "Heap Sort" << std::endl;
std::make_heap(data.begin(), data.end());
std::sort_heap(data.begin(), data.end());
}
};
class SortContext {
private:
SortingStrategy* strategy;
public:
SortContext(SortingStrategy* strategy) : strategy(strategy) {}
void setSortingStrategy(SortingStrategy* newStrategy) {
strategy = newStrategy;
}
void performSort(std::vector<int>& data) {
strategy->sort(data);
}
};
void main() {
std::vector<int> data = { 9, 3, 7, 1, 8, 5, 4 };
QuickSort quickSort;
SortContext context(&quickSort);
context.performSort(data);
HeapSort heapSort;
context.setSortingStrategy(&heapSort);
context.performSort(data);
}
전략 패턴 장단점
정렬 알고리즘 교체와 같이 새로운 전략 선택시
코드 변경을 최소화 할 수 있으며
각 "전략"을 개별 테스트할 때도 유용한다.
하지만 전략 교체에 따른 런타임 Overhead가 따를 수 있다.
상태 변화가 자주 발생하거나
Runtime에서 전략 변경이 필요한 경우 적합한 패턴
그냥 if 문으로 사용하면 안될까?
[예제] 조건문 사용
#include <iostream>
enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE };
double calculate(double a, double b, Operation op) {
if (op == ADD) {
return a + b;
}
else if (op == SUBTRACT) {
return a - b;
}
else if (op == MULTIPLY) {
return a * b;
}
else if (op == DIVIDE) {
if (b != 0) {
return a / b;
}
else {
std::cerr << "Division by zero is not allowed." << std::endl;
return 0.0;
}
}
else {
std::cerr << "Invalid operation." << std::endl;
return 0.0;
}
}
void main() {
double num1 = 10.0;
double num2 = 5.0;
double result = calculate(num1, num2, ADD);
std::cout << "덧셈 = " << result << std::endl;
result = calculate(num1, num2, SUBTRACT);
std::cout << "뺄셈 = " << result << std::endl;
result = calculate(num1, num2, MULTIPLY);
std::cout << "곱셈 = " << result << std::endl;
result = calculate(num1, num2, DIVIDE);
std::cout << "나눗셈 = " << result << std::endl;
}
[예제] 전략 패턴 적용
#include <iostream>
class OperationStrategy {
public:
virtual double operate(double a, double b) = 0;
};
class AddOperation : public OperationStrategy {
public:
double operate(double a, double b) override {
return a + b;
}
};
class SubtractOperation : public OperationStrategy {
public:
double operate(double a, double b) override {
return a - b;
}
};
class MultiplyOperation : public OperationStrategy {
public:
double operate(double a, double b) override {
return a * b;
}
};
class DivideOperation : public OperationStrategy {
public:
double operate(double a, double b) override {
if (b != 0) {
return a / b;
}
else {
std::cerr << "Division by zero is not allowed." << std::endl;
return 0.0;
}
}
};
class Calculator {
private:
OperationStrategy* strategy;
public:
Calculator(OperationStrategy* strategy) : strategy(strategy) {}
void setStrategy(OperationStrategy* newStrategy) {
strategy = newStrategy;
}
double calculate(double a, double b) {
return strategy->operate(a, b);
}
};
void main() {
double num1 = 10.0;
double num2 = 5.0;
Calculator calculator(new AddOperation());
double result = calculator.calculate(num1, num2);
std::cout << "덧셈 = " << result << std::endl;
calculator.setStrategy(new SubtractOperation());
result = calculator.calculate(num1, num2);
std::cout << "뺄셈 = " << result << std::endl;
calculator.setStrategy(new MultiplyOperation());
result = calculator.calculate(num1, num2);
std::cout << "곱셈 = " << result << std::endl;
calculator.setStrategy(new DivideOperation());
result = calculator.calculate(num1, num2);
std::cout << "나눗셈 = " << result << std::endl;
}
추상 클래스나 코드 길이만 봐서는 복잡성이 커진 것 같지만
새로운 연산 기능을 추가할 때, 클래스를 새로 만들면 된다.
개별 테스트시에도 다른 연산은 신경쓰지 않아도 된다.
이처럼 전략패턴은 조건문 없이도 알고리즘을 교체할 수 있다.
반응형
'까망 동네 > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴] 중재자 패턴 (Mediator Pattern) (0) | 2023.10.22 |
---|---|
[디자인 패턴] 빌더 패턴 (Builder Pattern) (2) | 2023.10.21 |
[디자인패턴] 싱글턴 패턴 (0) | 2023.10.14 |
[프로그래밍] 디자인 패턴 필요성 (0) | 2022.08.29 |
MVC 패턴이란? (0) | 2022.08.28 |
댓글