Skip to content

C++ Design Patterns

Overview

Design patterns are general solutions to common problems in software design. They represent best practices and provide templates for how to solve common design problems in specific situations. This chapter introduces common design patterns and their implementation in C++.

🏭 Creational Patterns

Singleton Pattern (Singleton)

cpp
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>

// Lazy singleton (Thread-safe)
class Singleton {
private:
    static std::unique_ptr<Singleton> instance_;
    static std::mutex mutex_;
    
    // Private constructor
    Singleton() {
        std::cout << "Singleton instance created" << std::endl;
    }
    
public:
    // Delete copy constructor and assignment operator
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    
    static Singleton* getInstance() {
        std::lock_guard<std::mutex> lock(mutex_);
        if (instance_ == nullptr) {
            instance_ = std::unique_ptr<Singleton>(new Singleton());
        }
        return instance_.get();
    }
    
    void doSomething() {
        std::cout << "Singleton is working..." << std::endl;
    }
};

// Static member definitions
std::unique_ptr<Singleton> Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

// Eager singleton (Thread-safe, C++11 guaranteed)
class EagerSingleton {
private:
    EagerSingleton() {
        std::cout << "EagerSingleton instance created" << std::endl;
    }
    
public:
    static EagerSingleton& getInstance() {
        static EagerSingleton instance;  // C++11 guarantees thread safety
        return instance;
    }
    
    EagerSingleton(const EagerSingleton&) = delete;
    EagerSingleton& operator=(const EagerSingleton&) = delete;
    
    void doSomething() {
        std::cout << "EagerSingleton is working..." << std::endl;
    }
};

// Factory method pattern
class Product {
public:
    virtual ~Product() = default;
    virtual void use() = 0;
};

class ConcreteProductA : public Product {
public:
    void use() override {
        std::cout << "Using product A" << std::endl;
    }
};

class ConcreteProductB : public Product {
public:
    void use() override {
        std::cout << "Using product B" << std::endl;
    }
};

class Creator {
public:
    virtual ~Creator() = default;
    virtual std::unique_ptr<Product> createProduct() = 0;
    
    void someOperation() {
        auto product = createProduct();
        product->use();
    }
};

class ConcreteCreatorA : public Creator {
public:
    std::unique_ptr<Product> createProduct() override {
        return std::make_unique<ConcreteProductA>();
    }
};

class ConcreteCreatorB : public Creator {
public:
    std::unique_ptr<Product> createProduct() override {
        return std::make_unique<ConcreteProductB>();
    }
};

// Builder pattern
class House {
private:
    std::string foundation_;
    std::string walls_;
    std::string roof_;
    
public:
    void setFoundation(const std::string& foundation) {
        foundation_ = foundation;
    }
    
    void setWalls(const std::string& walls) {
        walls_ = walls;
    }
    
    void setRoof(const std::string& roof) {
        roof_ = roof;
    }
    
    void show() {
        std::cout << "House: " << foundation_ << " + " << walls_ << " + " << roof_ << std::endl;
    }
};

class HouseBuilder {
public:
    virtual ~HouseBuilder() = default;
    virtual void buildFoundation() = 0;
    virtual void buildWalls() = 0;
    virtual void buildRoof() = 0;
    virtual std::unique_ptr<House> getHouse() = 0;
};

class ConcreteHouseBuilder : public HouseBuilder {
private:
    std::unique_ptr<House> house_;
    
public:
    ConcreteHouseBuilder() {
        house_ = std::make_unique<House>();
    }
    
    void buildFoundation() override {
        house_->setFoundation("Concrete foundation");
    }
    
    void buildWalls() override {
        house_->setWalls("Brick walls");
    }
    
    void buildRoof() override {
        house_->setRoof("Tile roof");
    }
    
    std::unique_ptr<House> getHouse() override {
        return std::move(house_);
    }
};

class Director {
public:
    std::unique_ptr<House> construct(HouseBuilder& builder) {
        builder.buildFoundation();
        builder.buildWalls();
        builder.buildRoof();
        return builder.getHouse();
    }
};

🔧 Structural Patterns

Adapter Pattern (Adapter)

cpp
#include <iostream>
#include <string>

// Target interface
class Target {
public:
    virtual ~Target() = default;
    virtual std::string request() {
        return "Target: Default behavior";
    }
};

// Class that needs to be adapted
class Adaptee {
public:
    std::string specificRequest() {
        return "Special request";
    }
};

// Adapter
class Adapter : public Target {
private:
    std::unique_ptr<Adaptee> adaptee_;
    
public:
    Adapter(std::unique_ptr<Adaptee> adaptee) : adaptee_(std::move(adaptee)) {}
    
    std::string request() override {
        return "Adapter: (converted) " + adaptee_->specificRequest();
    }
};

// Decorator pattern
class Component {
public:
    virtual ~Component() = default;
    virtual std::string operation() = 0;
};

class ConcreteComponent : public Component {
public:
    std::string operation() override {
        return "ConcreteComponent";
    }
};

class Decorator : public Component {
protected:
    std::unique_ptr<Component> component_;
    
public:
    Decorator(std::unique_ptr<Component> component) : component_(std::move(component)) {}
    
    std::string operation() override {
        return component_->operation();
    }
};

class ConcreteDecoratorA : public Decorator {
public:
    ConcreteDecoratorA(std::unique_ptr<Component> component) : Decorator(std::move(component)) {}
    
    std::string operation() override {
        return "ConcreteDecoratorA(" + Decorator::operation() + ")";
    }
};

class ConcreteDecoratorB : public Decorator {
public:
    ConcreteDecoratorB(std::unique_ptr<Component> component) : Decorator(std::move(component)) {}
    
    std::string operation() override {
        return "ConcreteDecoratorB(" + Decorator::operation() + ")";
    }
};

// Facade pattern
class SubsystemA {
public:
    std::string operationA() {
        return "SubsystemA: Ready!";
    }
    
    std::string operationZ() {
        return "SubsystemA: Start!";
    }
};

class SubsystemB {
public:
    std::string operationB() {
        return "SubsystemB: Ready!";
    }
    
    std::string operationY() {
        return "SubsystemB: Fire!";
    }
};

class Facade {
private:
    std::unique_ptr<SubsystemA> subsystemA_;
    std::unique_ptr<SubsystemB> subsystemB_;
    
public:
    Facade() : subsystemA_(std::make_unique<SubsystemA>()), 
               subsystemB_(std::make_unique<SubsystemB>()) {}
    
    std::string operation() {
        std::string result = "Facade initializing subsystem:\n";
        result += subsystemA_->operationA() + "\n";
        result += subsystemB_->operationB() + "\n";
        result += "Facade commanding subsystems to execute:\n";
        result += subsystemA_->operationZ() + "\n";
        result += subsystemB_->operationY();
        return result;
    }
};

🎭 Behavioral Patterns

Observer Pattern (Observer)

cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

class Observer {
public:
    virtual ~Observer() = default;
    virtual void update(const std::string& message) = 0;
};

class Subject {
private:
    std::vector<Observer*> observers_;
    std::string state_;
    
public:
    void attach(Observer* observer) {
        observers_.push_back(observer);
    }
    
    void detach(Observer* observer) {
        observers_.erase(
            std::remove(observers_.begin(), observers_.end(), observer),
            observers_.end()
        );
    }
    
    void notify() {
        for (Observer* observer : observers_) {
            observer->update(state_);
        }
    }
    
    void setState(const std::string& state) {
        state_ = state;
        notify();
    }
    
    std::string getState() const {
        return state_;
    }
};

class ConcreteObserver : public Observer {
private:
    std::string name_;
    
public:
    ConcreteObserver(const std::string& name) : name_(name) {}
    
    void update(const std::string& message) override {
        std::cout << name_ << " received update: " << message << std::endl;
    }
};

// Strategy pattern
class Strategy {
public:
    virtual ~Strategy() = default;
    virtual std::string doAlgorithm(const std::vector<std::string>& data) = 0;
};

class ConcreteStrategyA : public Strategy {
public:
    std::string doAlgorithm(const std::vector<std::string>& data) override {
        std::string result;
        for (const auto& item : data) {
            result += item + ",";
        }
        if (!result.empty()) {
            result.pop_back();  // Remove last comma
        }
        return result;
    }
};

class ConcreteStrategyB : public Strategy {
public:
    std::string doAlgorithm(const std::vector<std::string>& data) override {
        std::string result;
        for (auto it = data.rbegin(); it != data.rend(); ++it) {
            result += *it + ",";
        }
        if (!result.empty()) {
            result.pop_back();
        }
        return result;
    }
};

class Context {
private:
    std::unique_ptr<Strategy> strategy_;
    
public:
    Context(std::unique_ptr<Strategy> strategy) : strategy_(std::move(strategy)) {}
    
    void setStrategy(std::unique_ptr<Strategy> strategy) {
        strategy_ = std::move(strategy);
    }
    
    std::string doSomeBusinessLogic() {
        std::vector<std::string> data = {"a", "b", "c", "d", "e"};
        std::string result = strategy_->doAlgorithm(data);
        return "Context: Using strategy to sort data " + result;
    }
};

// Command pattern
class Command {
public:
    virtual ~Command() = default;
    virtual void execute() = 0;
};

class Receiver {
public:
    void doSomething(const std::string& a) {
        std::cout << "Receiver: Working on (" << a << ")" << std::endl;
    }
    
    void doSomethingElse(const std::string& b) {
        std::cout << "Receiver: Also working on (" << b << ")" << std::endl;
    }
};

class SimpleCommand : public Command {
private:
    std::string payload_;
    
public:
    explicit SimpleCommand(const std::string& payload) : payload_(payload) {}
    
    void execute() override {
        std::cout << "SimpleCommand: Simple thing (" << payload_ << ")" << std::endl;
    }
};

class ComplexCommand : public Command {
private:
    Receiver* receiver_;
    std::string a_;
    std::string b_;
    
public:
    ComplexCommand(Receiver* receiver, const std::string& a, const std::string& b)
        : receiver_(receiver), a_(a), b_(b) {}
    
    void execute() override {
        std::cout << "ComplexCommand: Complex things completed by receiver object" << std::endl;
        receiver_->doSomething(a_);
        receiver_->doSomethingElse(b_);
    }
};

class Invoker {
private:
    std::unique_ptr<Command> on_start_;
    std::unique_ptr<Command> on_finish_;
    
public:
    void setOnStart(std::unique_ptr<Command> command) {
        on_start_ = std::move(command);
    }
    
    void setOnFinish(std::unique_ptr<Command> command) {
        on_finish_ = std::move(command);
    }
    
    void doSomethingImportant() {
        std::cout << "Invoker: Does anyone want to do something before I start?" << std::endl;
        if (on_start_) {
            on_start_->execute();
        }
        
        std::cout << "Invoker: ...doing something important..." << std::endl;
        
        std::cout << "Invoker: Does anyone want to do something after I finish?" << std::endl;
        if (on_finish_) {
            on_finish_->execute();
        }
    }
};

🔄 Template Method Pattern

Algorithm Skeleton

cpp
#include <iostream>
#include <string>

class AbstractClass {
public:
    // Template method defines algorithm skeleton
    void templateMethod() {
        baseOperation1();
        requiredOperation1();
        baseOperation2();
        hook1();
        requiredOperation2();
        baseOperation3();
        hook2();
    }
    
protected:
    // Base operations already implemented
    void baseOperation1() {
        std::cout << "AbstractClass: I am doing most of the work" << std::endl;
    }
    
    void baseOperation2() {
        std::cout << "AbstractClass: But I let subclasses override some operations" << std::endl;
    }
    
    void baseOperation3() {
        std::cout << "AbstractClass: But I'm still doing most of the work" << std::endl;
    }
    
    // Abstract operations must be implemented in subclasses
    virtual void requiredOperation1() = 0;
    virtual void requiredOperation2() = 0;
    
    // Hook methods have default implementations, but can be overridden
    virtual void hook1() {}
    virtual void hook2() {}
};

class ConcreteClass1 : public AbstractClass {
protected:
    void requiredOperation1() override {
        std::cout << "ConcreteClass1: Implementing operation 1" << std::endl;
    }
    
    void requiredOperation2() override {
        std::cout << "ConcreteClass1: Implementing operation 2" << std::endl;
    }
};

class ConcreteClass2 : public AbstractClass {
protected:
    void requiredOperation1() override {
        std::cout << "ConcreteClass2: Implementing operation 1" << std::endl;
    }
    
    void requiredOperation2() override {
        std::cout << "ConcreteClass2: Implementing operation 2" << std::endl;
    }
    
    void hook1() override {
        std::cout << "ConcreteClass2: Override hook1" << std::endl;
    }
};

// State pattern
class Context;

class State {
public:
    virtual ~State() = default;
    virtual void handle1(Context* context) = 0;
    virtual void handle2(Context* context) = 0;
};

class Context {
private:
    std::unique_ptr<State> state_;
    
public:
    Context(std::unique_ptr<State> state) : state_(std::move(state)) {}
    
    void transitionTo(std::unique_ptr<State> state) {
        std::cout << "Context: Transitioning to new state" << std::endl;
        state_ = std::move(state);
    }
    
    void request1() {
        state_->handle1(this);
    }
    
    void request2() {
        state_->handle2(this);
    }
};

class ConcreteStateA : public State {
public:
    void handle1(Context* context) override;
    void handle2(Context* context) override {
        std::cout << "ConcreteStateA handling request2" << std::endl;
    }
};

class ConcreteStateB : public State {
public:
    void handle1(Context* context) override {
        std::cout << "ConcreteStateB handling request1" << std::endl;
    }
    
    void handle2(Context* context) override;
};

void ConcreteStateA::handle1(Context* context) {
    std::cout << "ConcreteStateA handling request1" << std::endl;
    std::cout << "ConcreteStateA wants to change context's state" << std::endl;
    context->transitionTo(std::make_unique<ConcreteStateB>());
}

void ConcreteStateB::handle2(Context* context) {
    std::cout << "ConcreteStateB handling request2" << std::endl;
    std::cout << "ConcreteStateB wants to change context's state" << std::endl;
    context->transitionTo(std::make_unique<ConcreteStateA>());
}

// Design pattern demonstration
class DesignPatternDemo {
public:
    static void singletonDemo() {
        std::cout << "=== Singleton Pattern Demo ===" << std::endl;
        
        auto* s1 = Singleton::getInstance();
        auto* s2 = Singleton::getInstance();
        
        std::cout << "Same instance? " << (s1 == s2 ? "Yes" : "No") << std::endl;
        
        s1->doSomething();
        
        auto& es1 = EagerSingleton::getInstance();
        auto& es2 = EagerSingleton::getInstance();
        
        std::cout << "Eager singleton same instance? " << (&es1 == &es2 ? "Yes" : "No") << std::endl;
    }
    
    static void factoryDemo() {
        std::cout << "\n=== Factory Method Demo ===" << std::endl;
        
        auto creatorA = std::make_unique<ConcreteCreatorA>();
        auto creatorB = std::make_unique<ConcreteCreatorB>();
        
        creatorA->someOperation();
        creatorB->someOperation();
    }
    
    static void builderDemo() {
        std::cout << "\n=== Builder Pattern Demo ===" << std::endl;
        
        Director director;
        ConcreteHouseBuilder builder;
        
        auto house = director.construct(builder);
        house->show();
    }
    
    static void adapterDemo() {
        std::cout << "\n=== Adapter Pattern Demo ===" << std::endl;
        
        auto adaptee = std::make_unique<Adaptee>();
        auto adapter = std::make_unique<Adapter>(std::move(adaptee));
        
        std::cout << adapter->request() << std::endl;
    }
    
    static void decoratorDemo() {
        std::cout << "\n=== Decorator Pattern Demo ===" << std::endl;
        
        auto component = std::make_unique<ConcreteComponent>();
        std::cout << "Client: I have a simple component:" << std::endl;
        std::cout << component->operation() << std::endl;
        
        auto decorator1 = std::make_unique<ConcreteDecoratorA>(std::move(component));
        auto decorator2 = std::make_unique<ConcreteDecoratorB>(std::move(decorator1));
        
        std::cout << "Client: Now I have a decorated component:" << std::endl;
        std::cout << decorator2->operation() << std::endl;
    }
    
    static void observerDemo() {
        std::cout << "\n=== Observer Pattern Demo ===" << std::endl;
        
        Subject subject;
        ConcreteObserver observer1("Observer 1");
        ConcreteObserver observer2("Observer 2");
        
        subject.attach(&observer1);
        subject.attach(&observer2);
        
        subject.setState("First state");
        subject.setState("Second state");
        
        subject.detach(&observer1);
        subject.setState("Third state");
    }
    
    static void strategyDemo() {
        std::cout << "\n=== Strategy Pattern Demo ===" << std::endl;
        
        Context context(std::make_unique<ConcreteStrategyA>());
        std::cout << context.doSomeBusinessLogic() << std::endl;
        
        context.setStrategy(std::make_unique<ConcreteStrategyB>());
        std::cout << context.doSomeBusinessLogic() << std::endl;
    }
    
    static void commandDemo() {
        std::cout << "\n=== Command Pattern Demo ===" << std::endl;
        
        Invoker invoker;
        invoker.setOnStart(std::make_unique<SimpleCommand>("Say Hi!"));
        
        Receiver receiver;
        invoker.setOnFinish(std::make_unique<ComplexCommand>(&receiver, "Send email", "Save report"));
        
        invoker.doSomethingImportant();
    }
    
    static void templateMethodDemo() {
        std::cout << "\n=== Template Method Demo ===" << std::endl;
        
        std::cout << "Same client code can work with different subclasses:" << std::endl;
        
        ConcreteClass1 class1;
        class1.templateMethod();
        
        std::cout << std::endl;
        
        ConcreteClass2 class2;
        class2.templateMethod();
    }
    
    static void stateDemo() {
        std::cout << "\n=== State Pattern Demo ===" << std::endl;
        
        Context context(std::make_unique<ConcreteStateA>());
        context.request1();
        context.request2();
    }
};

int main() {
    DesignPatternDemo::singletonDemo();
    DesignPatternDemo::factoryDemo();
    DesignPatternDemo::builderDemo();
    DesignPatternDemo::adapterDemo();
    DesignPatternDemo::decoratorDemo();
    DesignPatternDemo::observerDemo();
    DesignPatternDemo::strategyDemo();
    DesignPatternDemo::commandDemo();
    DesignPatternDemo::templateMethodDemo();
    DesignPatternDemo::stateDemo();
    
    return 0;
}

Summary

Design patterns provide verified solutions to help solve common design problems:

Pattern Classification

  • Creational Patterns: Object creation mechanisms
  • Structural Patterns: Object composition ways
  • Behavioral Patterns: Inter-object communication

Common Patterns

PatternPurposeApplicable Scenarios
SingletonEnsure unique instanceConfiguration management, logging
FactoryObject creationProduct family creation
ObserverEvent notificationsGUI, MVC architecture
StrategyAlgorithm selectionBehavior changes
DecoratorDynamic feature additionFeature extensions

Design Principles

  • Single Responsibility: One class handles one responsibility
  • Open/Closed Principle: Open for extension, closed for modification
  • Liskov Substitution: Subtypes can replace base types
  • Interface Segregation: Interfaces should be small and focused
  • Dependency Inversion: Depend on abstractions, not concretes

Best Practices

  • Choose patterns based on actual needs
  • Avoid over-engineering
  • Combine with SOLID principles
  • Consider performance implications
  • Keep code simple

C++ Features

  • Leverage RAII for resource management
  • Use smart pointers for automatic memory management
  • Templates and generic programming
  • Move semantics for performance optimization

Design patterns are important tools for software design, but should be used carefully in appropriate scenarios, avoiding pattern-for-the-sake usage.

Content is for learning and research only.