[2026] C++ Strategy Pattern 완벽 가이드 | 알고리즘 캡슐화와 런타임 교체
이 글의 핵심
C++ Strategy Pattern : 알고리즘 캡슐화와 런타임 교체. Strategy Pattern이란?. 왜 필요한가·기본 구조 (다형성).
Strategy Pattern이란? 왜 필요한가
문제 시나리오: 알고리즘 하드코딩
문제: 정렬 알고리즘을 선택하는 로직이 Context에 하드코딩되면, 새 알고리즘 추가 시 Context를 수정해야 합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 나쁜 예: 알고리즘 하드코딩
class Sorter {
public:
void sort(std::vector<int>& data, const std::string& algorithm) {
if (algorithm == "bubble") {
// 버블 정렬
} else if (algorithm == "quick") {
// 퀵 정렬
} else if (algorithm == "merge") {
// 병합 정렬
}
// 새 알고리즘 추가 시 여기 수정
}
};
해결: Strategy Pattern은 알고리즘을 캡슐화해 런타임에 교체 가능하게 합니다. 행동 패턴 시리즈·State 패턴과 “알고리즘 교체 vs 상태 전이”를 비교해 읽으면 좋습니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 좋은 예: Strategy Pattern
// 타입 정의
class SortStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
virtual ~SortStrategy() = default;
};
class BubbleSort : public SortStrategy {
void sort(std::vector<int>& data) override { /* ....*/ }
};
class Sorter {
public:
void setStrategy(std::unique_ptr<SortStrategy> s) {
strategy = std::move(s);
}
void sort(std::vector<int>& data) {
strategy->sort(data);
}
private:
std::unique_ptr<SortStrategy> strategy;
};
아래 코드는 mermaid를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
flowchart TD
context["Context (Sorter)"]
strategy["Strategy (SortStrategy)"]
bubble[BubbleSort]
quick[QuickSort]
merge[MergeSort]
context --> strategy
strategy <|-- bubble
strategy <|-- quick
strategy <|-- merge
목차
1. 기본 구조 (다형성)
최소 Strategy
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
class SortStrategy {
public:
virtual void sort(std::vector<int>& data) = 0;
virtual std::string name() const = 0;
virtual ~SortStrategy() = default;
};
class BubbleSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
for (size_t i = 0; i < data.size(); ++i) {
for (size_t j = 0; j < data.size() - i - 1; ++j) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
}
}
}
}
std::string name() const override { return "BubbleSort"; }
};
class QuickSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
std::sort(data.begin(), data.end());
}
std::string name() const override { return "QuickSort"; }
};
class Sorter {
public:
void setStrategy(std::unique_ptr<SortStrategy> s) {
strategy = std::move(s);
}
void sort(std::vector<int>& data) {
if (strategy) {
std::cout << "Using " << strategy->name() << '\n';
strategy->sort(data);
}
}
private:
std::unique_ptr<SortStrategy> strategy;
};
int main() {
Sorter sorter;
std::vector<int> data = {5, 2, 8, 1, 9};
sorter.setStrategy(std::make_unique<BubbleSort>());
sorter.sort(data); // Using BubbleSort
sorter.setStrategy(std::make_unique<QuickSort>());
sorter.sort(data); // Using QuickSort
}
2. 함수 포인터 방식
간단한 알고리즘
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <algorithm>
using SortFunc = void(*)(std::vector<int>&);
void bubbleSort(std::vector<int>& data) {
for (size_t i = 0; i < data.size(); ++i) {
for (size_t j = 0; j < data.size() - i - 1; ++j) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
}
}
}
}
void quickSort(std::vector<int>& data) {
std::sort(data.begin(), data.end());
}
class Sorter {
public:
void setStrategy(SortFunc func) {
strategy = func;
}
void sort(std::vector<int>& data) {
if (strategy) {
strategy(data);
}
}
private:
SortFunc strategy = nullptr;
};
int main() {
Sorter sorter;
std::vector<int> data = {5, 2, 8, 1, 9};
sorter.setStrategy(bubbleSort);
sorter.sort(data);
sorter.setStrategy(quickSort);
sorter.sort(data);
}
3. 람다 방식
인라인 알고리즘
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
class Sorter {
public:
using Strategy = std::function<void(std::vector<int>&)>;
void setStrategy(Strategy s) {
strategy = s;
}
void sort(std::vector<int>& data) {
if (strategy) {
strategy(data);
}
}
private:
Strategy strategy;
};
int main() {
Sorter sorter;
std::vector<int> data = {5, 2, 8, 1, 9};
// 람다로 Strategy 정의
sorter.setStrategy( {
std::sort(data.begin(), data.end());
});
sorter.sort(data);
// 역순 정렬
sorter.setStrategy( {
std::sort(data.begin(), data.end(), std::greater<>());
});
sorter.sort(data);
}
4. std::function 방식
유연한 Strategy
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
class PaymentStrategy {
public:
using Strategy = std::function<bool(double)>;
void setStrategy(Strategy s) {
strategy = s;
}
bool pay(double amount) {
if (strategy) {
return strategy(amount);
}
return false;
}
private:
Strategy strategy;
};
int main() {
PaymentStrategy payment;
// 신용카드
payment.setStrategy( {
std::cout << "Paying $" << amount << " with Credit Card\n";
return true;
});
payment.pay(100.0);
// PayPal
payment.setStrategy( {
std::cout << "Paying $" << amount << " with PayPal\n";
return true;
});
payment.pay(50.0);
}
5. 자주 발생하는 문제와 해결법
문제 1: Strategy nullptr
증상: 크래시. 원인: Strategy가 설정되지 않았습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 잘못된 사용: nullptr 검사 없음
void sort(std::vector<int>& data) {
strategy->sort(data); // Crash: nullptr
}
// ✅ 올바른 사용: nullptr 검사
void sort(std::vector<int>& data) {
if (strategy) {
strategy->sort(data);
} else {
throw std::runtime_error("Strategy not set");
}
}
문제 2: 상태 공유
증상: 예상과 다른 동작. 원인: Strategy가 상태를 가지면 재사용 시 문제가 됩니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 잘못된 사용: 상태 공유
class CountingSort : public SortStrategy {
int count = 0; // 상태
public:
void sort(std::vector<int>& data) override {
++count; // 재사용 시 누적
}
};
// ✅ 올바른 사용: 상태 없는 Strategy
class CountingSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
// 상태 없음, 순수 알고리즘
}
};
6. 프로덕션 패턴
패턴 1: 기본 Strategy
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Sorter {
public:
Sorter() : strategy(std::make_unique<QuickSort>()) {} // 기본값
void setStrategy(std::unique_ptr<SortStrategy> s) {
if (s) {
strategy = std::move(s);
}
}
void sort(std::vector<int>& data) {
strategy->sort(data); // 항상 유효
}
private:
std::unique_ptr<SortStrategy> strategy;
};
패턴 2: Strategy Factory
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class StrategyFactory {
public:
static std::unique_ptr<SortStrategy> create(const std::string& type) {
if (type == "bubble") return std::make_unique<BubbleSort>();
if (type == "quick") return std::make_unique<QuickSort>();
return nullptr;
}
};
int main() {
Sorter sorter;
sorter.setStrategy(StrategyFactory::create("quick"));
}
7. 완전한 예제: 압축 알고리즘
#include <iostream>
#include <string>
#include <memory>
#include <vector>
class CompressionStrategy {
public:
virtual std::vector<uint8_t> compress(const std::string& data) = 0;
virtual std::string decompress(const std::vector<uint8_t>& data) = 0;
virtual std::string name() const = 0;
virtual ~CompressionStrategy() = default;
};
class ZipCompression : public CompressionStrategy {
public:
std::vector<uint8_t> compress(const std::string& data) override {
std::cout << "[ZIP] Compressing " << data.size() << " bytes\n";
std::vector<uint8_t> result(data.begin(), data.end());
return result;
}
std::string decompress(const std::vector<uint8_t>& data) override {
std::cout << "[ZIP] Decompressing " << data.size() << " bytes\n";
return std::string(data.begin(), data.end());
}
std::string name() const override { return "ZIP"; }
};
class GzipCompression : public CompressionStrategy {
public:
std::vector<uint8_t> compress(const std::string& data) override {
std::cout << "[GZIP] Compressing " << data.size() << " bytes\n";
std::vector<uint8_t> result(data.begin(), data.end());
return result;
}
std::string decompress(const std::vector<uint8_t>& data) override {
std::cout << "[GZIP] Decompressing " << data.size() << " bytes\n";
return std::string(data.begin(), data.end());
}
std::string name() const override { return "GZIP"; }
};
class Compressor {
public:
void setStrategy(std::unique_ptr<CompressionStrategy> s) {
strategy = std::move(s);
}
std::vector<uint8_t> compress(const std::string& data) {
if (!strategy) {
throw std::runtime_error("Compression strategy not set");
}
std::cout << "Using " << strategy->name() << " compression\n";
return strategy->compress(data);
}
std::string decompress(const std::vector<uint8_t>& data) {
if (!strategy) {
throw std::runtime_error("Compression strategy not set");
}
return strategy->decompress(data);
}
private:
std::unique_ptr<CompressionStrategy> strategy;
};
int main() {
Compressor compressor;
std::string data = "Hello, World! This is a test.";
compressor.setStrategy(std::make_unique<ZipCompression>());
auto compressed = compressor.compress(data);
auto decompressed = compressor.decompress(compressed);
std::cout << "Result: " << decompressed << "\n\n";
compressor.setStrategy(std::make_unique<GzipCompression>());
compressed = compressor.compress(data);
decompressed = compressor.decompress(compressed);
std::cout << "Result: " << decompressed << '\n';
}
8. 성능 비교
| 방식 | 장점 | 단점 |
|---|---|---|
| 다형성 | 타입 안전, 확장 가능 | vtable 오버헤드, 힙 할당 |
| 함수 포인터 | 빠름, 간단 | 타입 안전 부족, 상태 없음 |
| 람다 | 인라인, 캡처 가능 | 타입 복잡, 디버깅 어려움 |
| std::function | 유연, 모든 callable | 오버헤드 큼, 힙 할당 |
정리
| 개념 | 설명 |
|---|---|
| Strategy Pattern | 알고리즘을 캡슐화해 런타임 교체 |
| 목적 | 알고리즘 독립성, 확장성 |
| 구조 | Context, Strategy, ConcreteStrategy |
| 장점 | OCP 준수, 조건문 제거, 테스트 용이 |
| 단점 | 클래스 증가, 간접 참조 |
| 사용 사례 | 정렬, 압축, 결제, 라우팅 |
| Strategy Pattern은 알고리즘을 동적으로 교체해야 하는 상황에서 강력한 디자인 패턴입니다. |
FAQ
Q1: Strategy Pattern은 언제 쓰나요?
A: 여러 알고리즘 중 선택해야 하고, 런타임에 교체가 필요할 때 사용합니다.
Q2: 다형성 vs 람다?
A: 확장성이 중요하면 다형성, 간단한 알고리즘이면 람다를 사용하세요.
Q3: State Pattern과 차이는?
A: Strategy는 알고리즘 교체, State는 상태 전이에 집중합니다.
Q4: 성능 오버헤드는?
A: 다형성은 vtable 조회, std::function은 힙 할당 오버헤드가 있습니다. 함수 포인터가 가장 빠릅니다.
Q5: 기본 Strategy는 어떻게 설정하나요?
A: 생성자에서 기본 Strategy를 설정하세요.
Q6: Strategy Pattern 학습 리소스는?
A:
- “Design Patterns” by Gang of Four
- “Head First Design Patterns” by Freeman & Freeman
- Refactoring Guru: Strategy Pattern 한 줄 요약: Strategy Pattern으로 알고리즘을 캡슐화하고 런타임에 교체할 수 있습니다. 다음으로 Command Pattern을 읽어보면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.