C++ Design Patterns | 'Singleton/Factory/Observer' Practical Guide

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;
    }
};

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

  1. Singleton: Ensure single instance
  2. Factory: Encapsulate object creation
  3. Observer: Notify state changes
  4. 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

Master design patterns for better C++ architecture! 🚀