C++ Design Patterns

Design patterns are named solutions to recurring structural problems — knowing them means recognizing 'oh, this is just a Strategy problem' instead of reinventing an ad hoc, harder-to-maintain version of the same idea.

Every non-trivial codebase eventually needs to solve the same handful of structural problems: how do you create objects without hard-coding a specific class name everywhere? how do you let behavior vary at runtime without an if/else ladder that grows forever? how do you notify interested parties about a change without coupling them tightly to the thing that changed? Design patterns are named, well-understood solutions to exactly these recurring problems, catalogued so that 'apply the Observer pattern here' communicates a whole design in three words to anyone who knows it. This page covers twelve of the patterns most likely to come up in real C++ code and in interviews — for each one: the problem it solves, its intent, its structure, a working C++ implementation (using modern idioms — smart pointers and virtual functions rather than raw new/delete), and when it earns its complexity versus when it's overkill. Patterns are tools, not goals: applying one where a plain function would do is itself a design mistake.

Singleton

Problem: some resource in a program genuinely must have exactly one instance — a logging system, a hardware device interface, a global configuration object — and every part of the program needs access to that same instance. Intent: guarantee a class has exactly one instance and provide a single global access point to it.

The 'Meyers singleton' pattern (a function-local static) is the idiomatic modern C++ way to implement this: the standard guarantees that a function-local static's initialization is thread-safe since C++11, so no manual locking is needed. Deleting the copy constructor and copy assignment operator prevents accidental duplication of the 'single' instance.

Advantages: guaranteed single instance, lazy initialization (created only when first needed), globally accessible without passing it through every function signature. Disadvantages: it's effectively global mutable state, which makes unit testing harder (tests can't easily swap in a fake Logger) and hides a dependency that would be more honest as an explicit constructor parameter; overuse of Singleton is a common sign of poor dependency management. Use it sparingly — genuinely singular, stateless-ish resources (a logger, perhaps) are reasonable; reaching for Singleton as a substitute for proper dependency injection is not.

class Logger
{
public:
    static Logger& instance()
    {
        static Logger single;   // constructed once, on first call, thread-safe since C++11
        return single;
    }

    void log(const std::string& message)
    {
        std::cout << "[LOG] " << message << "\n";
    }

    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

private:
    Logger() = default;
};

Factory Method and Abstract Factory

Problem: code that creates objects directly (Shape* s = new Circle();) is tightly coupled to the concrete class name, so adding a new shape type means hunting down and editing every place a shape gets created. Intent (Factory Method): defer object creation to a virtual function that subclasses override, so the calling code depends only on a base type. Intent (Abstract Factory): go one level further and produce entire families of related objects (a UI toolkit's Button, Checkbox, and Menu, matched consistently to one theme) through one factory interface, so the concrete family can be swapped as a unit.

main() never names Circle or Square directly — it only knows about the abstract Shape interface and a string identifying which kind to create. Adding a Triangle later means adding one branch inside createShape and a new class, with zero changes to any calling code.

Advantages: decouples client code from concrete classes, centralizes creation logic (validation, caching, or object pooling can be added in one place), makes adding new product types low-friction. Disadvantages: adds an extra layer of indirection and, for Abstract Factory specifically, a whole interface hierarchy — overkill if the set of concrete types is small and unlikely to grow. Use Factory Method whenever a class hierarchy is expected to grow with new subtypes over time; use Abstract Factory when whole families of related objects need to be swapped together consistently (e.g. switching a UI theme).

#include <memory>
#include <iostream>

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

class Circle : public Shape
{
public:
    void draw() const override { std::cout << "Drawing a circle\n"; }
};

class Square : public Shape
{
public:
    void draw() const override { std::cout << "Drawing a square\n"; }
};

// Factory Method: creation logic is centralized behind one function
std::unique_ptr<Shape> createShape(const std::string& kind)
{
    if (kind == "circle") return std::make_unique<Circle>();
    if (kind == "square") return std::make_unique<Square>();
    return nullptr;
}

int main()
{
    auto shape = createShape("circle");
    if (shape) shape->draw();
    return 0;
}

Builder

Problem: a constructor with ten optional parameters (Pizza(size, crust, cheese, pepperoni, mushrooms, olives, ...)) becomes unreadable at the call site (Pizza(12, true, false, true, false, true) — which flag was which?) and awkward to extend. Intent: separate the construction of a complex object from its representation, building it step by step through a fluent, named interface.

Each Builder method returns *this by reference, enabling the chained .size(16).pepperoni(true) syntax. Every option has a sensible default, so the call site only mentions the fields it actually cares about, in any order, with a self-documenting name attached to each value instead of a positional argument nobody can decode by reading the call alone.

Advantages: readable, self-documenting construction; sensible defaults for unset options; can validate the combination of options in one place (build()) before the final object exists. Disadvantages: more boilerplate than a plain constructor for a simple object with few fields. Use it when a type has many optional or defaultable construction parameters, or when construction needs to enforce ordering or validation across multiple steps.

#include <string>
#include <iostream>

class Pizza
{
public:
    class Builder
    {
    public:
        Builder& size(int inches)      { size_ = inches; return *this; }
        Builder& cheese(bool value)    { cheese_ = value; return *this; }
        Builder& pepperoni(bool value) { pepperoni_ = value; return *this; }

        Pizza build() const { return Pizza(size_, cheese_, pepperoni_); }

    private:
        int size_ = 12;
        bool cheese_ = true;
        bool pepperoni_ = false;
    };

    void describe() const
    {
        std::cout << size_ << "-inch pizza, cheese=" << cheese_
                  << ", pepperoni=" << pepperoni_ << "\n";
    }

private:
    Pizza(int size, bool cheese, bool pepperoni)
        : size_(size), cheese_(cheese), pepperoni_(pepperoni) {}

    int size_;
    bool cheese_;
    bool pepperoni_;
};

int main()
{
    Pizza pizza = Pizza::Builder().size(16).pepperoni(true).build();
    pizza.describe();
    return 0;
}

Adapter

Problem: an existing class has exactly the functionality you need, but its interface doesn't match what your code expects — a third-party JSON library's parse() method, say, when your code is written against your own Parser interface. Rewriting the third-party library isn't an option, and duplicating its logic under a new name would be wasteful and hard to keep in sync. Intent: wrap the incompatible interface in a thin translation layer that presents the interface your code actually expects.

render() is written entirely against IPrinter and has no idea LegacyPrinter exists. PrinterAdapter's print() method does the actual translation — converting a std::string to a const char* and forwarding the call — which is the entire pattern in one line of real logic.

Advantages: lets incompatible interfaces work together without modifying either side, isolates translation logic in one place. Disadvantages: adds a layer of indirection, and every call through the adapter pays a small translation cost. Use it whenever integrating a third-party or legacy component whose interface doesn't match your codebase's abstractions.

#include <iostream>

// Third-party class you can't change, with an interface that doesn't match yours
class LegacyPrinter
{
public:
    void printText(const char* text) { std::cout << "Legacy: " << text << "\n"; }
};

// The interface your code expects
class IPrinter
{
public:
    virtual ~IPrinter() = default;
    virtual void print(const std::string& text) = 0;
};

// Adapter: translates IPrinter calls into LegacyPrinter calls
class PrinterAdapter : public IPrinter
{
public:
    explicit PrinterAdapter(LegacyPrinter& legacy) : legacy_(legacy) {}

    void print(const std::string& text) override
    {
        legacy_.printText(text.c_str());
    }

private:
    LegacyPrinter& legacy_;
};

void render(IPrinter& printer)
{
    printer.print("Hello from application code");
}

int main()
{
    LegacyPrinter legacy;
    PrinterAdapter adapter(legacy);
    render(adapter);   // render() only ever talks to IPrinter
    return 0;
}

Observer

Problem: when one object's state changes, several other objects need to know about it — a UI element needs to refresh when underlying data changes, several subsystems need to react to a sensor reading — but hard-wiring the changed object to call each interested party by name creates tight coupling that breaks the moment the set of interested parties changes. Intent: define a one-to-many dependency so that when one object (the subject) changes state, all its registered observers are notified automatically, without the subject needing to know anything concrete about them.

TemperatureSensor knows only about the abstract Observer interface, never about Display specifically, so any number of unrelated observer types (a logger, an alarm, a network publisher) can attach without the sensor's code changing at all. Notification is a simple loop calling the same virtual method on everyone currently registered.

Advantages: loose coupling between subject and observers, supports adding/removing observers at runtime, one change point fans out to arbitrarily many listeners. Disadvantages: notification order is often unspecified and shouldn't be relied on; if observers hold raw pointers and an observer is destroyed without detaching, the subject is left holding a dangling pointer (a real hazard — prefer std::weak_ptr or an explicit detach() call in the observer's destructor). Use it for event systems, UI data-binding, and any publish/subscribe relationship.

#include <vector>
#include <memory>
#include <iostream>

class Observer
{
public:
    virtual ~Observer() = default;
    virtual void onTemperatureChanged(double celsius) = 0;
};

class TemperatureSensor
{
public:
    void attach(Observer* obs) { observers_.push_back(obs); }

    void setTemperature(double celsius)
    {
        temperature_ = celsius;
        for (Observer* obs : observers_)
            obs->onTemperatureChanged(temperature_);
    }

private:
    double temperature_ = 0.0;
    std::vector<Observer*> observers_;
};

class Display : public Observer
{
public:
    void onTemperatureChanged(double celsius) override
    {
        std::cout << "Display updated: " << celsius << "C\n";
    }
};

int main()
{
    TemperatureSensor sensor;
    Display display;
    sensor.attach(&display);

    sensor.setTemperature(23.5);   // Display updated: 23.5C
    return 0;
}

Strategy

Problem: an algorithm needs to vary independently of the code that uses it — sorting with different comparison rules, compressing with different algorithms, routing with different pathfinding strategies — and hard-coding the choice with an if/else or switch on a type tag grows unmanageable as the number of variants increases and duplicates the same dispatch logic everywhere the choice is needed. Intent: define a family of interchangeable algorithms behind a common interface, and let the algorithm be selected (and swapped) independently of the client code that uses it.

Sorter never contains any sorting logic itself — it only holds a SortStrategy and delegates to it, so swapping strategy_ at runtime (via setStrategy) changes behavior without touching Sorter's own code at all. In modern C++, a std::function<void(std::vector<int>&)> holding a lambda is often a lighter-weight alternative to a full class hierarchy for simple strategies.

Advantages: eliminates conditional logic for selecting behavior, makes adding a new algorithm a matter of adding a new class rather than editing existing code (open/closed principle), enables runtime strategy swapping. Disadvantages: introduces an interface and at least one class per strategy, which is unnecessary ceremony if there are only ever two simple variants — a single if/else or a std::function parameter may be simpler. Use it when the number of interchangeable algorithms is meaningful or expected to grow, or when the strategy needs to be selected/changed at runtime.

#include <memory>
#include <vector>
#include <algorithm>
#include <iostream>

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

class AscendingSort : public SortStrategy
{
public:
    void sort(std::vector<int>& data) const override
    {
        std::sort(data.begin(), data.end());
    }
};

class DescendingSort : public SortStrategy
{
public:
    void sort(std::vector<int>& data) const override
    {
        std::sort(data.begin(), data.end(), std::greater<int>());
    }
};

class Sorter
{
public:
    explicit Sorter(std::unique_ptr<SortStrategy> strategy) : strategy_(std::move(strategy)) {}

    void setStrategy(std::unique_ptr<SortStrategy> strategy) { strategy_ = std::move(strategy); }

    void sortData(std::vector<int>& data) const { strategy_->sort(data); }

private:
    std::unique_ptr<SortStrategy> strategy_;
};

int main()
{
    std::vector<int> values = {5, 2, 8, 1, 9};

    Sorter sorter(std::make_unique<AscendingSort>());
    sorter.sortData(values);   // 1 2 5 8 9

    sorter.setStrategy(std::make_unique<DescendingSort>());
    sorter.sortData(values);   // 9 8 5 2 1

    return 0;
}

State

Problem: an object's behavior needs to change completely based on its internal state (a TCP connection behaves differently depending on whether it's Closed, Listening, or Established), and representing that with a status enum plus a giant switch statement in every method scatters the same state-dependent branching throughout the class. Intent: encapsulate state-specific behavior in separate state classes and delegate to the current state object, so transitioning state is just swapping which object is 'current.'

Connection itself contains zero state-dependent logic — every open()/close() call is delegated to whichever State object is currently installed, and each state's methods decide both the behavior for the current state and which state to transition into next.

Advantages: removes sprawling conditional logic tied to state, makes each state's behavior and its legal transitions easy to find in one class, makes adding a new state low-risk. Disadvantages: introduces a class per state, which is overkill for a type with only two simple states; state objects can be shared as flyweights if they hold no per-instance data, avoiding repeated allocation. Use it whenever an object's behavior genuinely branches on a multi-way internal state machine, especially one with more than two or three states.

#include <memory>
#include <iostream>

class Connection;

class State
{
public:
    virtual ~State() = default;
    virtual void open(Connection& conn) = 0;
    virtual void close(Connection& conn) = 0;
};

class Connection
{
public:
    explicit Connection(std::unique_ptr<State> initial) : state_(std::move(initial)) {}

    void setState(std::unique_ptr<State> state) { state_ = std::move(state); }
    void open() { state_->open(*this); }
    void close() { state_->close(*this); }

private:
    std::unique_ptr<State> state_;
};

class ClosedState : public State
{
public:
    void open(Connection& conn) override;
    void close(Connection&) override { std::cout << "Already closed\n"; }
};

class OpenState : public State
{
public:
    void open(Connection&) override { std::cout << "Already open\n"; }
    void close(Connection& conn) override
    {
        std::cout << "Closing connection\n";
        conn.setState(std::make_unique<ClosedState>());
    }
};

void ClosedState::open(Connection& conn)
{
    std::cout << "Opening connection\n";
    conn.setState(std::make_unique<OpenState>());
}

int main()
{
    Connection conn(std::make_unique<ClosedState>());
    conn.open();    // Open
…

Command

Problem: a UI or system needs to queue, log, undo, or otherwise treat an action as a first-class object, rather than as an immediate function call — a text editor's undo stack, a remote-control button whose action can be reassigned, a job queue processed later or on another thread. Intent: encapsulate a request as an object, so the request (its parameters and the action to perform) can be stored, passed around, queued, or reversed independently of whoever issued it.

Each AppendCommand bundles both the action (execute) and its exact reversal (undo), along with the parameters needed for both. Storing executed commands in a history vector turns 'undo' into 'call undo() on the most recent entry', which generalizes cleanly to a full undo/redo stack.

Advantages: decouples the invoker of an action from the object that performs it, makes undo/redo, logging, and queuing straightforward since the action itself is a storable object. Disadvantages: a class per command type adds boilerplate for very simple actions, where a std::function might suffice instead. Use it for undo/redo systems, task queues, macro recording, or any scenario where an action must outlive the moment it was requested.

#include <memory>
#include <vector>
#include <iostream>

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

class Document
{
public:
    void appendText(const std::string& text) { content_ += text; }
    void removeLast(std::size_t n) { content_.erase(content_.size() - n); }
    const std::string& content() const { return content_; }

private:
    std::string content_;
};

class AppendCommand : public Command
{
public:
    AppendCommand(Document& doc, std::string text) : doc_(doc), text_(std::move(text)) {}

    void execute() override { doc_.appendText(text_); }
    void undo() override { doc_.removeLast(text_.size()); }

private:
    Document& doc_;
    std::string text_;
};

int main()
{
    Document doc;
    std::vector<std::unique_ptr<Command>> history;

    auto cmd = std::make_unique<AppendCommand>(doc, "Hello, ");
    cmd->execute();
    history.push_back(std::move(cmd));

    std::cout << doc.content() << "\n";   // "Hello, "

    history.back()->undo();
    std::cout << "[" << doc.content() << "]\n";   // "[]"

    return 0;
}

Template Method

Problem: several related algorithms share the same overall skeleton (open a file, read it, process it, close it) but differ in one or two specific steps, and duplicating the shared skeleton in every variant means fixing a bug in that skeleton requires fixing it everywhere it was copied. Intent: define the skeleton of an algorithm in a base class method, deferring only the steps that vary to virtual functions that subclasses override — the skeleton itself is never duplicated or overridden.

process() is the template method: it's a concrete, non-virtual function that calls three steps in a fixed order. loadData() and saveData() have sensible default implementations subclasses can optionally override, while transformData() is pure virtual, forcing every subclass to supply the one step that genuinely differs.

Advantages: eliminates duplicated algorithm skeletons, localizes each variant's differences to just the overridden steps, keeps the overall control flow in one place. Disadvantages: relies on inheritance (a subclass is locked into the base's skeleton at compile time), which is less flexible than composition-based alternatives like Strategy when the skeleton itself might also need to vary. Use it when multiple algorithms share an identical structure and differ only in specific, well-defined steps.

#include <iostream>

class DataProcessor
{
public:
    virtual ~DataProcessor() = default;

    // Template method: the fixed skeleton, not virtual, never overridden
    void process()
    {
        loadData();
        transformData();
        saveData();
    }

protected:
    virtual void loadData() { std::cout << "Loading data from default source\n"; }
    virtual void transformData() = 0;   // must be provided by every subclass
    virtual void saveData() { std::cout << "Saving to default sink\n"; }
};

class CsvProcessor : public DataProcessor
{
protected:
    void transformData() override { std::cout << "Parsing CSV rows\n"; }
};

int main()
{
    CsvProcessor processor;
    processor.process();   // Loading..., Parsing CSV rows, Saving...
    return 0;
}

Visitor

Problem: a fixed hierarchy of types (say, different AST node types in a compiler: NumberNode, AddNode, MultiplyNode) needs several unrelated operations performed on it (evaluate, pretty-print, type-check), and adding each new operation as a virtual method on every node type bloats the hierarchy and forces recompiling every node type each time a new operation is added. Intent: separate an operation from the object structure it operates on by having each element accept a visitor object, letting a new operation be added as a single new visitor class without touching the element classes at all.

This is 'double dispatch': expr->accept(eval) dispatches on expr's dynamic type first, and inside accept, visitor.visit(*this) dispatches again on the visitor's overload matching the now-known concrete node type — together they pick the exact visit(NumberNode&) or visit(AddNode&) overload needed, something a single virtual call couldn't do alone. Adding a PrettyPrintVisitor later requires zero changes to NumberNode or AddNode.

Advantages: adding a new operation is one new class, with no changes to the element hierarchy. Disadvantages: adding a new element type requires updating every existing visitor (the trade-off is exactly inverted from ordinary virtual dispatch), and the double-dispatch machinery is genuinely harder to read than a straightforward virtual method. Use it when the set of element types is stable but the set of operations performed on them is expected to grow — compilers and interpreters are the classic example.

#include <memory>
#include <vector>
#include <iostream>

class NumberNode;
class AddNode;

class Visitor
{
public:
    virtual ~Visitor() = default;
    virtual void visit(NumberNode& node) = 0;
    virtual void visit(AddNode& node) = 0;
};

class Expr
{
public:
    virtual ~Expr() = default;
    virtual void accept(Visitor& visitor) = 0;
};

class NumberNode : public Expr
{
public:
    explicit NumberNode(int value) : value(value) {}
    void accept(Visitor& visitor) override { visitor.visit(*this); }
    int value;
};

class AddNode : public Expr
{
public:
    AddNode(std::unique_ptr<Expr> l, std::unique_ptr<Expr> r) : left(std::move(l)), right(std::move(r)) {}
    void accept(Visitor& visitor) override { visitor.visit(*this); }
    std::unique_ptr<Expr> left, right;
};

class EvalVisitor : public Visitor
{
public:
    void visit(NumberNode& node) override { result = node.value; }
    void visit(AddNode& node) override
    {
        node.left->accept(*this);
        int leftVal = result;
        node.right->accept(*this);
        result = leftVal + result;
    }
    int result = 0;
};

int main()
{
    auto expr = std::make_unique<AddNode>(std::make_unique<NumberNode>(3), std::ma
…

Decorator

Problem: a class needs optional, combinable extra responsibilities added at runtime — a text stream that can optionally be compressed and/or encrypted, in any combination — and creating a subclass for every combination (CompressedStream, EncryptedStream, CompressedEncryptedStream, ...) explodes combinatorially. Intent: attach additional responsibilities to an object dynamically by wrapping it in decorator objects that share the same interface, so decorators can be layered in any combination at runtime.

Each decorator wraps a TextSource and is itself a TextSource, so decorators nest arbitrarily deep and in any order — BracketDecorator wrapping UpperCaseDecorator wrapping PlainText applies uppercasing first, then brackets, and swapping their order would change the result, showing the decorators genuinely compose.

Advantages: adds responsibilities at runtime without subclass explosion, decorators compose freely in any combination or order. Disadvantages: many small wrapper objects can make debugging (and stack traces) harder to follow, and getting the wrapping order right is the caller's responsibility, not something the type system enforces. Use it whenever optional, combinable behavior needs to be layered onto an object rather than baked into a fixed hierarchy of subclasses.

#include <memory>
#include <string>
#include <iostream>

class TextSource
{
public:
    virtual ~TextSource() = default;
    virtual std::string getText() const = 0;
};

class PlainText : public TextSource
{
public:
    explicit PlainText(std::string text) : text_(std::move(text)) {}
    std::string getText() const override { return text_; }
private:
    std::string text_;
};

// Base decorator: wraps another TextSource and forwards by default
class TextDecorator : public TextSource
{
public:
    explicit TextDecorator(std::unique_ptr<TextSource> inner) : inner_(std::move(inner)) {}
protected:
    std::unique_ptr<TextSource> inner_;
};

class UpperCaseDecorator : public TextDecorator
{
public:
    using TextDecorator::TextDecorator;
    std::string getText() const override
    {
        std::string text = inner_->getText();
        for (char& c : text) c = static_cast<char>(std::toupper(c));
        return text;
    }
};

class BracketDecorator : public TextDecorator
{
public:
    using TextDecorator::TextDecorator;
    std::string getText() const override { return "[" + inner_->getText() + "]"; }
};

int main()
{
    std::unique_ptr<TextSource> text = std::make_unique<PlainText>("
…

Proxy

Problem: direct access to an object is undesirable for some reason — it's expensive to create (a large image that shouldn't load until actually needed), it's remote (a network service), or it needs access control (only authorized callers may use it) — but callers should still be able to use it through the same interface as if they had the real thing. Intent: provide a stand-in object that controls access to another object, implementing the same interface and forwarding to the real object when appropriate.

ImageProxy implements the same Image interface as RealImage, so callers can't tell the difference — but the expensive loadFromDisk() work is deferred until display() is actually called the first time, and real_ being non-null on subsequent calls skips reloading entirely.

Advantages: transparent to callers, enables lazy initialization, remote access, caching, or access control without changing client code. Disadvantages: adds a layer of indirection and, for a naive proxy, potential inconsistency if the real object's state can change out from under a cache. Use it for lazy loading of expensive resources, controlling access to a sensitive object, or providing a local stand-in for a remote one.

#include <memory>
#include <iostream>

class Image
{
public:
    virtual ~Image() = default;
    virtual void display() = 0;
};

class RealImage : public Image
{
public:
    explicit RealImage(std::string filename) : filename_(std::move(filename))
    {
        loadFromDisk();   // expensive
    }
    void display() override { std::cout << "Displaying " << filename_ << "\n"; }
private:
    void loadFromDisk() { std::cout << "Loading " << filename_ << " from disk...\n"; }
    std::string filename_;
};

// Lazy-loading proxy: defers the expensive construction until first use
class ImageProxy : public Image
{
public:
    explicit ImageProxy(std::string filename) : filename_(std::move(filename)) {}

    void display() override
    {
        if (!real_)
            real_ = std::make_unique<RealImage>(filename_);
        real_->display();
    }

private:
    std::string filename_;
    std::unique_ptr<RealImage> real_;
};

int main()
{
    ImageProxy image("vacation.png");
    std::cout << "Proxy created, image not loaded yet\n";

    image.display();   // triggers the actual load, then displays
    image.display();   // already loaded: displays immediately

    return 0;
}

Dependency Injection

Problem: a class that creates its own dependencies internally (class OrderService { Database db; }; with db constructed inside OrderService) is hard to test in isolation (you can't substitute a fake database) and hard to reconfigure (every OrderService is permanently wired to the same concrete Database type). Intent: rather than a class constructing its own dependencies, those dependencies are supplied ('injected') from outside — typically through the constructor — so the class depends only on an abstract interface, and the concrete implementation can be swapped freely, especially for testing.

OrderService never names PostgresDatabase or FakeDatabase — it only depends on the abstract Database interface, received through its constructor. Swapping in FakeDatabase for the test case requires zero changes to OrderService itself, which is exactly the testability win dependency injection provides.

Advantages: makes unit testing straightforward (inject a fake or mock), decouples a class from the concrete implementations of what it depends on, makes configuration explicit rather than hidden inside constructors. Disadvantages: pushes the responsibility of wiring dependencies together up to whoever constructs the object graph (main(), or a dedicated container), which can feel like extra ceremony in a small program. Use it as close to a default habit as any pattern on this page — almost any class with a non-trivial external dependency (a database, a network client, a clock) benefits from receiving it rather than constructing it internally.

#include <memory>
#include <iostream>

class Database
{
public:
    virtual ~Database() = default;
    virtual void save(const std::string& record) = 0;
};

class PostgresDatabase : public Database
{
public:
    void save(const std::string& record) override
    {
        std::cout << "Saving to Postgres: " << record << "\n";
    }
};

class FakeDatabase : public Database
{
public:
    void save(const std::string& record) override
    {
        std::cout << "[test] pretending to save: " << record << "\n";
        savedRecords.push_back(record);
    }
    std::vector<std::string> savedRecords;
};

// OrderService depends on the Database ABSTRACTION, injected via constructor
class OrderService
{
public:
    explicit OrderService(std::shared_ptr<Database> db) : db_(std::move(db)) {}

    void placeOrder(const std::string& item) { db_->save("order: " + item); }

private:
    std::shared_ptr<Database> db_;
};

int main()
{
    auto realDb = std::make_shared<PostgresDatabase>();
    OrderService production(realDb);
    production.placeOrder("widget");   // Saving to Postgres: order: widget

    auto fakeDb = std::make_shared<FakeDatabase>();
    OrderService test(fakeDb);
    test.placeOr
…

Choosing a Pattern Without Overengineering

Every pattern on this page adds indirection, and indirection has a real cost: more classes to read, more places behavior can hide, and a steeper on-ramp for anyone new to the code. The test that separates good pattern use from cargo-cult pattern use is simple: can you name the specific, concrete problem the pattern solves in this code, right now, not hypothetically? 'We might need to swap databases someday' is a much weaker justification for Dependency Injection than 'we need to run this against a fake database in every unit test, starting today.' A single if/else is not a design smell; a switch statement that keeps growing every time a new type is added, scattered across a dozen files, usually is.

Why is a Meyers singleton (a function-local static instance) preferred over a manually managed static pointer with a null check for implementing Singleton in modern C++?

A function-local static's initialization is guaranteed thread-safe by the C++11 standard — if multiple threads call the accessor function concurrently before the instance exists, the standard guarantees exactly one thread performs the initialization and the others wait for it to finish. A manually managed static pointer with an if (ptr == nullptr) check is a classic data race under concurrent first-time access unless you add your own locking, which the Meyers singleton gets for free from the language.

What's the key structural difference between Factory Method and Abstract Factory?

Factory Method centers on producing one kind of product through a single overridable creation method, deferring which concrete subtype gets created. Abstract Factory produces entire families of related products (several different product types that need to be consistent with each other, like a UI theme's button, checkbox, and menu) through one factory interface, so an entire family can be swapped as a unit rather than one product type at a time.

Why does the Visitor pattern make adding a new operation cheap but adding a new element type expensive, while ordinary virtual dispatch has the opposite trade-off?

In Visitor, an operation is a new class implementing visit() for every existing element type, so adding an operation touches nothing in the element hierarchy — but adding a new element type requires adding a new visit() overload to every existing Visitor subclass. Ordinary virtual dispatch is the mirror image: adding a new subclass is cheap (just implement the existing virtual methods), but adding a new virtual method to the base class requires touching every existing subclass.

How does the Decorator pattern avoid the combinatorial subclass explosion that would result from creating one subclass per combination of optional behaviors?

Instead of a subclass per combination, each optional behavior is its own decorator class implementing the same interface as the object it wraps, and decorators can be nested in any combination or order at runtime by wrapping one decorator's output in another. N independent optional behaviors need only N decorator classes, rather than up to 2^N subclasses to cover every combination.

Why does Dependency Injection make unit testing significantly easier than a class that constructs its own dependencies internally?

When a dependency (like a database or network client) is injected through the constructor as an abstract interface, a test can supply a fake or mock implementation of that interface instead of the real one, letting the test run in isolation without a real database, network call, or other external system. A class that constructs its concrete dependency internally offers no such substitution point — every test that exercises that class also exercises the real dependency.

Write a C++ program using the Strategy pattern to implement two different discount calculation strategies (a percentage discount and a flat-amount discount) for a ShoppingCart class, and demonstrate switching between them at runtime.

ShoppingCart holds only a DiscountStrategy pointer and delegates entirely to whichever strategy is currently set, so checkout() never changes when a new discount type is added. Setting PercentageDiscount(10.0) then checking out $100 yields $90, and switching to FlatDiscount(15.0) on the same cart yields $85 — the discount logic changes purely by swapping which strategy object is installed, with zero changes to ShoppingCart itself.

What does the following program print, and why does the Observer pattern shown here decouple Subject from ConcreteObserver?

It prints 'Printer sees: 21' followed by 'Doubler sees: 42', in that order, since observers are notified in the order they were attached. Subject only ever calls the abstract Observer::update() and holds a vector of Observer*, so it has no idea Printer or Doubler exist as concrete types — any new observer type can attach without a single line of Subject's code changing, which is the decoupling the pattern provides.

Write a C++ program implementing a simple Command pattern for a remote control that can turn a Light on or off, storing executed commands so the most recent one can be undone.

LightOnCommand bundles both the action (execute -> light.on()) and its precise reversal (undo -> light.off()) into one object, which is pushed onto a history vector after running. The program prints 'Light is ON' when execute() runs and 'Light is OFF' when undo() is called on the same stored command, demonstrating that the remote control's invoking code never needs to know what a specific command does internally — only that every Command supports execute() and undo().

#include <vector>
#include <iostream>

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

class Subject
{
public:
    void attach(Observer* obs) { observers_.push_back(obs); }
    void setValue(int value)
    {
        value_ = value;
        for (Observer* obs : observers_)
            obs->update(value_);
    }
private:
    int value_ = 0;
    std::vector<Observer*> observers_;
};

class Printer : public Observer
{
public:
    void update(int value) override { std::cout << "Printer sees: " << value << "\n"; }
};

class Doubler : public Observer
{
public:
    void update(int value) override { std::cout << "Doubler sees: " << value * 2 << "\n"; }
};

int main()
{
    Subject subject;
    Printer printer;
    Doubler doubler;

    subject.attach(&printer);
    subject.attach(&doubler);

    subject.setValue(21);
    return 0;
}