C++ 디자인 패턴 | "싱글톤/팩토리/옵저버" 실전 가이드

C++ 디자인 패턴 | "싱글톤/팩토리/옵저버" 실전 가이드

이 글의 핵심

C++ 디자인 패턴에 대해 정리한 개발 블로그 글입니다. class Singleton { private: static Singleton instance; Singleton() {}

같은 싱글톤·팩토리 아이디어는 JavaScriptPython 데코레이터에서도 자주 쓰입니다. 심화는 C++ 생성 패턴 가이드·종합 가이드를 보세요.

1. 싱글톤 패턴 (Singleton)

기본 구현

class Singleton {
private:
    static Singleton* instance;
    
    Singleton() {}  // private 생성자
    
public:
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;

스레드 안전 싱글톤

#include <mutex>

class ThreadSafeSingleton {
private:
    static ThreadSafeSingleton* instance;
    static mutex mtx;
    
    ThreadSafeSingleton() {}
    
public:
    static ThreadSafeSingleton* getInstance() {
        if (instance == nullptr) {
            lock_guard<mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new ThreadSafeSingleton();
            }
        }
        return instance;
    }
};

Meyers 싱글톤 (권장)

class MeyersSingleton {
private:
    MeyersSingleton() {}
    
public:
    static MeyersSingleton& getInstance() {
        static MeyersSingleton instance;  // C++11부터 스레드 안전
        return instance;
    }
    
    MeyersSingleton(const MeyersSingleton&) = delete;
    MeyersSingleton& operator=(const MeyersSingleton&) = delete;
};

2. 팩토리 패턴 (Factory)

심플 팩토리

enum class ShapeType { Circle, Rectangle, Triangle };

class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() {}
};

class Circle : public Shape {
public:
    void draw() override { cout << "원 그리기" << endl; }
};

class Rectangle : public Shape {
public:
    void draw() override { cout << "사각형 그리기" << endl; }
};

class ShapeFactory {
public:
    static unique_ptr<Shape> createShape(ShapeType type) {
        switch (type) {
            case ShapeType::Circle:
                return make_unique<Circle>();
            case ShapeType::Rectangle:
                return make_unique<Rectangle>();
            default:
                return nullptr;
        }
    }
};

int main() {
    auto shape = ShapeFactory::createShape(ShapeType::Circle);
    shape->draw();
}

추상 팩토리

class Button {
public:
    virtual void render() = 0;
    virtual ~Button() {}
};

class WindowsButton : public Button {
public:
    void render() override { cout << "Windows 버튼" << endl; }
};

class MacButton : public Button {
public:
    void render() override { cout << "Mac 버튼" << endl; }
};

class GUIFactory {
public:
    virtual unique_ptr<Button> createButton() = 0;
    virtual ~GUIFactory() {}
};

class WindowsFactory : public GUIFactory {
public:
    unique_ptr<Button> createButton() override {
        return make_unique<WindowsButton>();
    }
};

class MacFactory : public GUIFactory {
public:
    unique_ptr<Button> createButton() override {
        return make_unique<MacButton>();
    }
};

3. 옵저버 패턴 (Observer)

#include <vector>
#include <algorithm>

class Observer {
public:
    virtual void update(int value) = 0;
    virtual ~Observer() {}
};

class Subject {
private:
    vector<Observer*> observers;
    int state;
    
public:
    void attach(Observer* observer) {
        observers.push_back(observer);
    }
    
    void detach(Observer* observer) {
        observers.erase(
            remove(observers.begin(), observers.end(), observer),
            observers.end()
        );
    }
    
    void setState(int newState) {
        state = newState;
        notify();
    }
    
    void notify() {
        for (auto observer : observers) {
            observer->update(state);
        }
    }
};

class ConcreteObserver : public Observer {
private:
    string name;
    
public:
    ConcreteObserver(string n) : name(n) {}
    
    void update(int value) override {
        cout << name << "이(가) 업데이트 받음: " << value << endl;
    }
};

int main() {
    Subject subject;
    
    ConcreteObserver obs1("관찰자1");
    ConcreteObserver obs2("관찰자2");
    
    subject.attach(&obs1);
    subject.attach(&obs2);
    
    subject.setState(10);
    subject.setState(20);
}

4. 전략 패턴 (Strategy)

class SortStrategy {
public:
    virtual void sort(vector<int>& data) = 0;
    virtual ~SortStrategy() {}
};

class BubbleSort : public SortStrategy {
public:
    void sort(vector<int>& data) override {
        cout << "버블 정렬" << endl;
        // 구현...
    }
};

class QuickSort : public SortStrategy {
public:
    void sort(vector<int>& data) override {
        cout << "퀵 정렬" << endl;
        // 구현...
    }
};

class Sorter {
private:
    unique_ptr<SortStrategy> strategy;
    
public:
    void setStrategy(unique_ptr<SortStrategy> s) {
        strategy = move(s);
    }
    
    void sort(vector<int>& data) {
        if (strategy) {
            strategy->sort(data);
        }
    }
};

int main() {
    Sorter sorter;
    vector<int> data = {5, 2, 8, 1, 9};
    
    sorter.setStrategy(make_unique<BubbleSort>());
    sorter.sort(data);
    
    sorter.setStrategy(make_unique<QuickSort>());
    sorter.sort(data);
}

5. 데코레이터 패턴 (Decorator)

class Coffee {
public:
    virtual string getDescription() = 0;
    virtual double cost() = 0;
    virtual ~Coffee() {}
};

class SimpleCoffee : public Coffee {
public:
    string getDescription() override {
        return "커피";
    }
    
    double cost() override {
        return 2.0;
    }
};

class CoffeeDecorator : public Coffee {
protected:
    unique_ptr<Coffee> coffee;
    
public:
    CoffeeDecorator(unique_ptr<Coffee> c) : coffee(move(c)) {}
};

class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(unique_ptr<Coffee> c) : CoffeeDecorator(move(c)) {}
    
    string getDescription() override {
        return coffee->getDescription() + " + 우유";
    }
    
    double cost() override {
        return coffee->cost() + 0.5;
    }
};

class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(unique_ptr<Coffee> c) : CoffeeDecorator(move(c)) {}
    
    string getDescription() override {
        return coffee->getDescription() + " + 설탕";
    }
    
    double cost() override {
        return coffee->cost() + 0.2;
    }
};

int main() {
    auto coffee = make_unique<SimpleCoffee>();
    coffee = make_unique<MilkDecorator>(move(coffee));
    coffee = make_unique<SugarDecorator>(move(coffee));
    
    cout << coffee->getDescription() << endl;  // 커피 + 우유 + 설탕
    cout << coffee->cost() << "달러" << endl;  // 2.7달러
}

실전 예시

예시 1: 로깅 시스템 (싱글톤 + 전략)

enum class LogLevel { Debug, Info, Warning, Error };

class LogStrategy {
public:
    virtual void log(const string& message) = 0;
    virtual ~LogStrategy() {}
};

class ConsoleLogger : public LogStrategy {
public:
    void log(const string& message) override {
        cout << "[Console] " << message << endl;
    }
};

class FileLogger : public LogStrategy {
public:
    void log(const string& message) override {
        // 파일에 로그 작성
        cout << "[File] " << message << endl;
    }
};

class Logger {
private:
    unique_ptr<LogStrategy> strategy;
    LogLevel minLevel;
    
    Logger() : minLevel(LogLevel::Info) {
        strategy = make_unique<ConsoleLogger>();
    }
    
public:
    static Logger& getInstance() {
        static Logger instance;
        return instance;
    }
    
    void setStrategy(unique_ptr<LogStrategy> s) {
        strategy = move(s);
    }
    
    void log(LogLevel level, const string& message) {
        if (level >= minLevel && strategy) {
            strategy->log(message);
        }
    }
};

int main() {
    Logger::getInstance().log(LogLevel::Info, "시스템 시작");
    Logger::getInstance().setStrategy(make_unique<FileLogger>());
    Logger::getInstance().log(LogLevel::Error, "에러 발생");
}

예시 2: 플러그인 시스템 (팩토리)

class Plugin {
public:
    virtual void execute() = 0;
    virtual string getName() = 0;
    virtual ~Plugin() {}
};

class AudioPlugin : public Plugin {
public:
    void execute() override { cout << "오디오 처리" << endl; }
    string getName() override { return "AudioPlugin"; }
};

class VideoPlugin : public Plugin {
public:
    void execute() override { cout << "비디오 처리" << endl; }
    string getName() override { return "VideoPlugin"; }
};

class PluginFactory {
private:
    map<string, function<unique_ptr<Plugin>()>> creators;
    
public:
    void registerPlugin(const string& name, function<unique_ptr<Plugin>()> creator) {
        creators[name] = creator;
    }
    
    unique_ptr<Plugin> create(const string& name) {
        if (creators.find(name) != creators.end()) {
            return creators[name]();
        }
        return nullptr;
    }
};

int main() {
    PluginFactory factory;
    
    factory.registerPlugin("audio",  { return make_unique<AudioPlugin>(); });
    factory.registerPlugin("video",  { return make_unique<VideoPlugin>(); });
    
    auto plugin = factory.create("audio");
    plugin->execute();
}

자주 발생하는 문제

문제 1: 싱글톤 소멸 순서

증상: 프로그램 종료 시 크래시

원인: 싱글톤 간 의존성

해결법: Meyers 싱글톤 사용 또는 의존성 제거

문제 2: 팩토리에서 메모리 누수

증상: 메모리 누수

원인: raw 포인터 반환

해결법: unique_ptr 반환

문제 3: 옵저버 댕글링 포인터

증상: 크래시

원인: 옵저버 소멸 후에도 Subject가 참조

해결법: detach 호출 또는 weak_ptr 사용

FAQ

Q1: 디자인 패턴은 언제 사용하나요?

A: 반복되는 문제에 대한 검증된 해결책이 필요할 때 사용합니다.

Q2: 모든 패턴을 알아야 하나요?

A: 아니요, 자주 쓰이는 5-10개 패턴만 알아도 충분합니다.

Q3: 패턴을 무조건 사용해야 하나요?

A: 아니요, 과도한 패턴 사용은 복잡도만 높입니다. 필요할 때만 사용하세요.

Q4: C++에서 가장 유용한 패턴은?

A: RAII, 싱글톤, 팩토리, 옵저버, 전략 패턴이 가장 자주 사용됩니다.

Q5: 패턴 학습 순서는?

A:

  1. 싱글톤, 팩토리 (생성 패턴)
  2. 전략, 옵저버 (행동 패턴)
  3. 데코레이터, 어댑터 (구조 패턴)

Q6: 디자인 패턴 책 추천은?

A: “Design Patterns” (GoF), “Head First Design Patterns”


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ Pimpl Idiom | “Pointer to Implementation” 가이드
  • C++ 클래스와 객체 | “초보자를 위한” 완벽 가이드 [그림으로 이해]
  • C++ 상속과 다형성 | “virtual 함수” 완벽 가이드

관련 글

  • C++ 클래스와 객체 |