Design Patterns
Factory, Singleton, Strategy, Observer, and friends — and the thread-safety fix the classic Singleton needs.
Advanced
class Coffee { public: virtual ~Coffee() = default; virtual std::string type() const = 0; };
class Espresso : public Coffee { public: std::string type() const override { return "Espresso"; } };
class Cappuccino : public Coffee { public: std::string type() const override { return "Cappuccino"; } };
std::unique_ptr<Coffee> makeCoffee(const std::string &kind) {
if (kind == "espresso") return std::make_unique<Espresso>();
if (kind == "cappuccino") return std::make_unique<Cappuccino>();
return nullptr;
}
WindowsFactory vs.
MacFactory, each producing a matching button, checkbox, and menu). Builder
separates constructing a complex object step by step from what the finished object looks like
— useful when an object has many optional configuration steps that would otherwise
demand a constructor with a dozen parameters.
class PizzaBuilder {
std::vector<std::string> toppings;
public:
PizzaBuilder & addTopping(const std::string &t) { toppings.push_back(t); return *this; } // chainable
std::string build() const {
std::string result = "Pizza with:";
for (auto &t : toppings) result += " " + t;
return result;
}
};
auto pizza = PizzaBuilder().addTopping("cheese").addTopping("mushroom").build();
new'd on first access, never
deleted — has two real problems: it's not thread-safe (two threads can both
see a null pointer and both allocate), and it leaks deliberately. The modern, preferred form
uses a function-local static instead:
class Logger {
Logger() { std::cout << "logger initialized\n"; } // private ctor blocks direct instantiation
public:
Logger(const Logger &) = delete; // and blocks copying the single instance
static Logger & instance() {
static Logger single; // constructed on first call, guaranteed thread-safe since C++11
return single; // destructed automatically at program exit — no leak, no manual delete
}
void log(const std::string &msg) { std::cout << msg << '\n'; }
};
Logger::instance().log("started"); // no visible constructor call anywhere — only instance() reaches it
static is thread-safe — concurrent calls to instance() before
the object exists will block until exactly one of them finishes constructing it. That
guarantee is what makes this version correct without any manual locking, unlike the raw
lazily-new'd pointer version.
class SortStrategy { public: virtual void sort(std::vector<int> &v) = 0; virtual ~SortStrategy() = default; };
class QuickSort : public SortStrategy { public: void sort(std::vector<int> &v) override { std::sort(v.begin(), v.end()); } };
class ReverseSort : public SortStrategy { public: void sort(std::vector<int> &v) override { std::sort(v.rbegin(), v.rend()); } };
class Sorter {
std::unique_ptr<SortStrategy> strategy;
public:
Sorter(std::unique_ptr<SortStrategy> s) : strategy(std::move(s)) {}
void setStrategy(std::unique_ptr<SortStrategy> s) { strategy = std::move(s); }
void execute(std::vector<int> &v) { strategy->sort(v); }
};
Publisher (the "subject") keeps a list of
Subscriber observers and notifies all of them when its state changes, without
needing to know anything about what each observer actually does in response.
class Publisher;
class Subscriber { public: virtual void update(Publisher *p) = 0; virtual ~Subscriber() = default; };
class Publisher {
std::vector<Subscriber*> subscribers;
public:
void attach(Subscriber *s) { subscribers.push_back(s); }
void notify() { for (auto *s : subscribers) s->update(this); }
};
Subscriber* here assumes the Publisher doesn't own
its subscribers' lifetimes — a textbook aggregation relationship, in the terms from
OOP Relationships.
class Beverage { public: virtual std::string describe() const = 0; virtual ~Beverage() = default; };
class Espresso : public Beverage { public: std::string describe() const override { return "Espresso"; } };
class MilkDecorator : public Beverage {
std::unique_ptr<Beverage> wrapped;
public:
MilkDecorator(std::unique_ptr<Beverage> b) : wrapped(std::move(b)) {}
std::string describe() const override { return wrapped->describe() + " + milk"; }
};
std::unique_ptr<Beverage> drink = std::make_unique<MilkDecorator>(std::make_unique<Espresso>());
drink->describe(); // "Espresso + milk"