C++ 디자인 패턴 — Singleton·Factory·Observer·Strategy 구현
개요 — 디자인 패턴이란
Section titled “개요 — 디자인 패턴이란”디자인 패턴(Design Pattern)은 반복되는 소프트웨어 설계 문제에 대한 검증된 해결책 템플릿입니다. GoF(Gang of Four)가 정리한 23가지 패턴 중 C++ 실무와 면접에서 가장 자주 나오는 Singleton, Factory, Observer, Strategy를 C++17 기준으로 구현합니다.
| 패턴 | 분류 | 핵심 의도 |
|---|---|---|
| Singleton | 생성(Creational) | 인스턴스를 하나만 보장 |
| Factory (Method/Abstract) | 생성(Creational) | 객체 생성을 서브클래스에 위임 |
| Observer | 행동(Behavioral) | 이벤트 구독/발행 |
| Strategy | 행동(Behavioral) | 알고리즘을 교체 가능하게 캡슐화 |
1. Singleton — 유일한 인스턴스 보장
Section titled “1. Singleton — 유일한 인스턴스 보장”1.1 기본 구현 (C++11 스레드 안전)
Section titled “1.1 기본 구현 (C++11 스레드 안전)”class AudioManager{public: // 전역 접근점 — C++11: 정적 지역 변수 초기화는 스레드 안전 static AudioManager& GetInstance() { static AudioManager instance; // Magic Static — 딱 한 번 초기화 return instance; }
void PlaySound(const std::string& id) { /* ... */ } void StopAll() { /* ... */ }
// 복사·이동 금지 AudioManager(const AudioManager&) = delete; AudioManager& operator=(const AudioManager&) = delete; AudioManager(AudioManager&&) = delete; AudioManager& operator=(AudioManager&&) = delete;
private: AudioManager() = default; // 외부 생성 금지 ~AudioManager() = default;};
// 사용AudioManager::GetInstance().PlaySound("explosion");1.2 CRTP 기반 재사용 가능한 Singleton
Section titled “1.2 CRTP 기반 재사용 가능한 Singleton”template<typename T>class Singleton{public: static T& GetInstance() { static T instance; return instance; }
Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete;
protected: Singleton() = default; virtual ~Singleton() = default;};
// 사용class ConfigManager : public Singleton<ConfigManager>{ friend class Singleton<ConfigManager>;
ConfigManager() { // 설정 파일 로드 }
public: std::string Get(const std::string& key) const { return config_.at(key); } void Set(const std::string& key, std::string value) { config_[key] = std::move(value); }
private: std::unordered_map<std::string, std::string> config_;};
ConfigManager::GetInstance().Set("MaxPlayers", "16");1.3 주의사항
Section titled “1.3 주의사항”장점: 전역 상태를 하나의 객체로 집중 관리단점: 단위 테스트 어려움 (전역 상태), 과도한 의존성, 소멸 순서 불확실대안: 의존성 주입(DI)으로 테스트 가능성 개선2. Factory Pattern — 객체 생성 캡슐화
Section titled “2. Factory Pattern — 객체 생성 캡슐화”2.1 Factory Method
Section titled “2.1 Factory Method”// 추상 제품struct Shape{ virtual double Area() const = 0; virtual std::string Name() const = 0; virtual ~Shape() = default;};
// 구체 제품struct Circle : Shape{ double radius; explicit Circle(double r) : radius(r) {} double Area() const override { return 3.14159 * radius * radius; } std::string Name() const override { return "Circle"; }};
struct Rectangle : Shape{ double width, height; Rectangle(double w, double h) : width(w), height(h) {} double Area() const override { return width * height; } std::string Name() const override { return "Rectangle"; }};
struct Triangle : Shape{ double base, height; Triangle(double b, double h) : base(b), height(h) {} double Area() const override { return 0.5 * base * height; } std::string Name() const override { return "Triangle"; }};2.2 등록 기반 Factory (확장에 유리)
Section titled “2.2 등록 기반 Factory (확장에 유리)”class ShapeFactory{public: using Creator = std::function<std::unique_ptr<Shape>()>;
// 타입 등록 static void Register(const std::string& type, Creator creator) { GetRegistry()[type] = std::move(creator); }
// 생성 static std::unique_ptr<Shape> Create(const std::string& type) { auto& registry = GetRegistry(); auto it = registry.find(type); if (it == registry.end()) throw std::invalid_argument("Unknown shape: " + type); return it->second(); }
private: static std::unordered_map<std::string, Creator>& GetRegistry() { static std::unordered_map<std::string, Creator> registry; return registry; }};
// 등록ShapeFactory::Register("circle", []{ return std::make_unique<Circle>(1.0); });ShapeFactory::Register("rectangle", []{ return std::make_unique<Rectangle>(2.0, 3.0); });
// 사용 — 새 도형 추가 시 Factory 코드 수정 불필요auto shape = ShapeFactory::Create("circle");std::cout << shape->Name() << ": " << shape->Area() << "\n";3. Observer Pattern — 이벤트 구독/발행
Section titled “3. Observer Pattern — 이벤트 구독/발행”#include <functional>#include <unordered_map>#include <vector>
// 이벤트 시스템 구현template<typename... Args>class Event{public: using HandlerID = uint64_t; using Handler = std::function<void(Args...)>;
// 구독 HandlerID Subscribe(Handler handler) { auto id = ++next_id_; handlers_[id] = std::move(handler); return id; }
// 구독 해제 void Unsubscribe(HandlerID id) { handlers_.erase(id); }
// 발행 void Broadcast(Args... args) const { for (const auto& [id, handler] : handlers_) handler(args...); }
private: std::unordered_map<HandlerID, Handler> handlers_; HandlerID next_id_ = 0;};
// 사용 예class Player{public: Event<int> OnHealthChanged; // int: new health Event<std::string> OnItemPickedUp; // string: item name
void TakeDamage(int damage) { health_ -= damage; OnHealthChanged.Broadcast(health_); }
void PickUpItem(const std::string& item) { OnItemPickedUp.Broadcast(item); }
private: int health_ = 100;};
// 구독자 등록Player player;
auto hpId = player.OnHealthChanged.Subscribe([](int hp) { std::cout << "UI 갱신: HP = " << hp << "\n";});
player.OnHealthChanged.Subscribe([](int hp) { if (hp <= 20) std::cout << "경고: HP 위험!\n";});
player.OnItemPickedUp.Subscribe([](const std::string& item) { std::cout << "인벤토리에 추가: " << item << "\n";});
player.TakeDamage(30); // UI 갱신: HP = 70player.TakeDamage(55); // UI 갱신: HP = 15 + 경고 출력player.PickUpItem("검"); // 인벤토리에 추가: 검
player.OnHealthChanged.Unsubscribe(hpId); // 구독 해제전통적인 Observer 인터페이스 방식
Section titled “전통적인 Observer 인터페이스 방식”// Observer 인터페이스class IHealthObserver{public: virtual void OnHealthChanged(int newHealth) = 0; virtual ~IHealthObserver() = default;};
// Subjectclass HealthComponent{public: void AddObserver(IHealthObserver* observer) { observers_.push_back(observer); } void RemoveObserver(IHealthObserver* observer) { observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), observers_.end()); }
private: void NotifyObservers(int health) { for (auto* obs : observers_) obs->OnHealthChanged(health); }
std::vector<IHealthObserver*> observers_; int health_ = 100;};4. Strategy Pattern — 알고리즘 교체 가능하게 캡슐화
Section titled “4. Strategy Pattern — 알고리즘 교체 가능하게 캡슐화”// 정렬 전략 인터페이스class ISortStrategy{public: virtual void Sort(std::vector<int>& data) = 0; virtual std::string Name() const = 0; virtual ~ISortStrategy() = default;};
// 구체 전략들class BubbleSort : public ISortStrategy{public: void Sort(std::vector<int>& data) override { for (size_t i = 0; i < data.size(); ++i) for (size_t j = 0; j + 1 < data.size() - i; ++j) if (data[j] > data[j+1]) std::swap(data[j], data[j+1]); } std::string Name() const override { return "BubbleSort"; }};
class QuickSort : public ISortStrategy{public: void Sort(std::vector<int>& data) override { std::sort(data.begin(), data.end()); } std::string Name() const override { return "QuickSort"; }};
// Context — 전략을 사용하는 클래스class Sorter{public: explicit Sorter(std::unique_ptr<ISortStrategy> strategy) : strategy_(std::move(strategy)) {}
// 런타임에 전략 교체 가능 void SetStrategy(std::unique_ptr<ISortStrategy> strategy) { strategy_ = std::move(strategy); }
void Sort(std::vector<int>& data) { std::cout << strategy_->Name() << " 사용\n"; strategy_->Sort(data); }
private: std::unique_ptr<ISortStrategy> strategy_;};
// 사용std::vector<int> data = {5, 3, 8, 1, 9, 2};
Sorter sorter(std::make_unique<BubbleSort>());sorter.Sort(data); // BubbleSort 사용
sorter.SetStrategy(std::make_unique<QuickSort>());sorter.Sort(data); // QuickSort 사용
// std::function을 이용한 경량 전략using SortFn = std::function<void(std::vector<int>&)>;
class FlexSorter{ SortFn strategy_;public: explicit FlexSorter(SortFn fn) : strategy_(std::move(fn)) {} void SetStrategy(SortFn fn) { strategy_ = std::move(fn); } void Sort(std::vector<int>& data) { strategy_(data); }};
FlexSorter fs([](std::vector<int>& d) { std::sort(d.begin(), d.end()); });fs.Sort(data);5. 패턴 선택 가이드
Section titled “5. 패턴 선택 가이드”| 상황 | 권장 패턴 |
|---|---|
| 전역 상태를 단일 인스턴스로 관리 | Singleton |
| 생성 로직이 복잡하거나, 타입에 따라 객체 종류 결정 | Factory |
| 이벤트 발생 시 여러 객체에 통지 | Observer |
| 동일한 작업을 다양한 알고리즘으로 수행 | Strategy |
| 런타임 알고리즘 교체 불필요, 정적 다형성 선호 | CRTP(Template Method) |
패턴 = 문제 + 해결책 + 트레이드오프- Singleton: Magic Static(C++11)으로 스레드 안전하게 구현. 테스트 어려우므로 DI로 대체 검토.
- Factory: 등록 기반 팩토리(
std::function+ map)로 OCP(개방-폐쇄 원칙) 달성. - Observer: 함수형 핸들러(
std::function)로 인터페이스 없이 경량 구현 가능. - Strategy:
std::unique_ptr<Interface>또는std::function으로 런타임 교체 구현.
모든 패턴은 “변하는 것을 캡슐화하라” 는 원칙의 구체적 적용입니다.