C++ Design Patterns | 'Singleton/Factory/Observer' Practical Guide
이 글의 핵심
class Singleton { private: static Singleton instance; Singleton() {}.
Same singleton/factory ideas are frequently used in JavaScript and Python decorators. For advanced topics, see C++ Creational Patterns Guide and Comprehensive Guide.
1. Singleton Pattern
Basic Implementation
Here is detailed implementation code using C++. Define a class to encapsulate data and functionality and perform branching with conditionals. Understand the role of each part while examining the code.
class Singleton {
private:
static Singleton* instance;
Singleton() {} // private constructor
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;
Thread-Safe Singleton
Here is detailed implementation code using C++. Import the necessary modules, define a class to encapsulate data and functionality, and perform branching with conditionals. Understand the role of each part while examining the code.
#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 Singleton (Recommended)
Below is an implementation example using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.
class MeyersSingleton {
private:
MeyersSingleton() {}
public:
static MeyersSingleton& getInstance() {
static MeyersSingleton instance; // Thread-safe from C++11
return instance;
}
MeyersSingleton(const MeyersSingleton&) = delete;
MeyersSingleton& operator=(const MeyersSingleton&) = delete;
};
2. Factory Pattern
Simple Factory
Here is detailed implementation code using C++. Define a class to encapsulate data and functionality and perform branching with conditionals. Understand the role of each part while examining the code.
enum class ShapeType { Circle, Rectangle, Triangle };
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
public:
void draw() override { cout << "Drawing circle" << endl; }
};
class Rectangle : public Shape {
public:
void draw() override { cout << "Drawing rectangle" << 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();
}
Abstract Factory
Here is detailed implementation code using C++. Define a class to encapsulate data and functionality. Understand the role of each part while examining the code.
class Button {
public:
virtual void render() = 0;
virtual ~Button() {}
};
class WindowsButton : public Button {
public:
void render() override { cout << "Windows button" << endl; }
};
class MacButton : public Button {
public:
void render() override { cout << "Mac button" << 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 Pattern
Here is detailed implementation code using C++. Import the necessary modules, define a class to encapsulate data and functionality, and process data with loops. Understand the role of each part while examining the code.
#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 << " received update: " << value << endl;
}
};
int main() {
Subject subject;
ConcreteObserver obs1("Observer1");
ConcreteObserver obs2("Observer2");
subject.attach(&obs1);
subject.attach(&obs2);
subject.setState(10);
subject.setState(20);
}
4. Strategy Pattern
Here is detailed implementation code using C++. Define a class to encapsulate data and functionality and perform branching with conditionals. Understand the role of each part while examining the code.
class SortStrategy {
public:
virtual void sort(vector<int>& data) = 0;
virtual ~SortStrategy() {}
};
class BubbleSort : public SortStrategy {
public:
void sort(vector<int>& data) override {
cout << "Bubble sort" << endl;
// Implementation...
}
};
class QuickSort : public SortStrategy {
public:
void sort(vector<int>& data) override {
cout << "Quick sort" << endl;
// Implementation...
}
};
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);
}
Summary
Key Points
- Singleton: Ensure single instance
- Factory: Encapsulate object creation
- Observer: Notify state changes
- Strategy: Encapsulate algorithms
When to Use
✅ Use design patterns when:
- Need proven solutions
- Want maintainable code
- Team communication
- Scalable architecture
❌ Don’t use when:
- Over-engineering simple problems
- Forcing patterns unnecessarily
- Team unfamiliar with patterns
Best Practices
- ✅ Use Meyers Singleton for thread safety
- ✅ Use smart pointers in factories
- ✅ Prefer composition over inheritance
- ❌ Don’t overuse patterns
- ❌ Don’t ignore SOLID principles
Related Articles
Master design patterns for better C++ architecture! 🚀