creational: factory method

Decouples "create an object" from the code that uses it — the caller asks for a product by some identifier and gets back a base-class pointer, never naming the concrete class directly. Adding a new product later means adding a new subclass and a new branch in the factory function; every existing caller keeps compiling unchanged. This is the same idea covered as the "virtual constructor" idiom in Inheritance & Polymorphism.

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

creational: abstract factory & builder

Abstract Factory is Factory Method one level up: instead of one factory function producing one kind of product, an abstract factory interface produces a whole family of related products (e.g. a UI toolkit's 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();
            

creational: singleton, done safely

Guarantees exactly one instance of a class exists, reachable globally. The classic textbook version — a raw pointer, lazily 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
            
The C++11 standard specifically guarantees that initialization of a function-local 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.
Worth saying explicitly: Singleton is one of the more controversial patterns in modern design — it introduces global mutable state and makes unit testing harder (you can't swap in a fake instance easily). Reach for it deliberately, not as a default way to share an object across a codebase.
The Logger above, as a full runnable program tracing exactly when construction happens: TopNotchNote/cpp/design_patterns_singleton_logger.cpp

behavioral: strategy

"Program to an interface, not an implementation" made concrete: instead of a class hard-coding one algorithm, it holds a pointer/reference to a strategy interface and delegates to it — swapping the algorithm at runtime just means swapping which concrete strategy object is plugged in.

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

behavioral: observer

One-to-many notification: a 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); }
};
            
Storing raw Subscriber* here assumes the Publisher doesn't own its subscribers' lifetimes — a textbook aggregation relationship, in the terms from OOP Relationships.

structural: adapter and decorator

Adapter wraps an existing class to present the interface some other code expects, without modifying the original — the classic use is bridging a legacy or third-party API to the interface your code already relies on. Decorator wraps an object to add behavior around it, and can be stacked — each decorator holds a reference to the thing it wraps and adds something before or after delegating to it.

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"
            

behavioral: iterator

Provides a way to access a collection's elements sequentially without exposing its internal structure — which is exactly what the STL's iterator abstraction already is. It's included here for completeness as a named pattern, but in C++ you'll almost never hand-write one from scratch; you'll write algorithms and containers that already speak the standard library's iterator interface.

where to go from here

Inheritance & Polymorphism — the virtual dispatch every pattern here relies on.
OOP Relationships — composition over inheritance, the design principle behind Strategy and Decorator.
Smart Pointers — ownership, used throughout these examples in place of raw new/delete.

reference

refactoring.guru — design patterns in C++