C++ 디자인 패턴 | "싱글톤/팩토리/옵저버" 실전 가이드
이 글의 핵심
C++ 디자인 패턴에 대해 정리한 개발 블로그 글입니다. class Singleton { private: static Singleton instance; Singleton() {}
같은 싱글톤·팩토리 아이디어는 JavaScript와 Python 데코레이터에서도 자주 쓰입니다. 심화는 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:
- 싱글톤, 팩토리 (생성 패턴)
- 전략, 옵저버 (행동 패턴)
- 데코레이터, 어댑터 (구조 패턴)
Q6: 디자인 패턴 책 추천은?
A: “Design Patterns” (GoF), “Head First Design Patterns”
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Pimpl Idiom | “Pointer to Implementation” 가이드
- C++ 클래스와 객체 | “초보자를 위한” 완벽 가이드 [그림으로 이해]
- C++ 상속과 다형성 | “virtual 함수” 완벽 가이드
관련 글
- C++ 클래스와 객체 |