Creational Patterns
Creational patterns deal with object creation mechanisms. They help you create objects in a manner suitable for the situation, making your code more flexible and reusable.
What are Design Patterns?
Think of design patterns as proven recipes for solving common programming problems. Just like a chef doesn't reinvent how to make a sauce every time, programmers use design patterns as templates for solving recurring design challenges. These patterns were popularized by the "Gang of Four" (GoF) in their influential book "Design Patterns: Elements of Reusable Object-Oriented Software" (1994).
Design patterns are not code you can copy-paste. They are descriptions or templates for how to solve a problem that can be used in many different situations. Learning patterns helps you communicate with other developers using a shared vocabulary and write code that's easier to maintain and extend.
Design Pattern
A design pattern is a general, reusable solution to a commonly occurring problem in software design. It's not a finished design that can be transformed directly into code - it's a template for how to solve a problem that can be applied in many situations.
Design patterns were popularized by the "Gang of Four" (GoF) in their influential 1994 book. They provide a shared vocabulary for developers and help create code that's easier to maintain and extend.
Three categories: Creational (object creation), Structural (object composition), Behavioral (object interaction and responsibility).
The Singleton Pattern
The Singleton pattern ensures a class has only one instance and provides a global point of access to it. Think of it like having only one president of a country, only one sun in our solar system, or only one configuration manager in your application.
When would you use Singleton? Common examples include logging systems (you want all log messages to go to the same place), configuration managers (one source of truth for settings), connection pools (manage a shared pool of database connections), and hardware interface access (only one object should control the printer).
#include <iostream>
#include <string>
#include <mutex>
class Logger {
public:
// Delete copy constructor and assignment operator
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
= delete, making it impossible to copy or assign Logger instances. This is crucial because the entire point of Singleton is having exactly one instance - allowing copies would defeat the purpose. These deleted functions will trigger compile-time errors if anyone tries to copy the Logger.
// Static method to get the single instance
static Logger& getInstance() {
static Logger instance; // Thread-safe in C++11 and later
return instance;
}
getInstance() static method is the global access point to the single Logger instance. It uses the "Meyers Singleton" technique with a static local variable that's initialized on first call. In C++11 and later, this initialization is guaranteed to be thread-safe - if multiple threads call getInstance() simultaneously, the initialization happens exactly once. The instance persists for the program's lifetime and is automatically destroyed at program termination.
void log(const std::string& message) {
std::lock_guard<std::mutex> lock(mutex_);
std::cout << "[LOG] " << message << std::endl;
}
log() method is thread-safe thanks to std::lock_guard, which automatically acquires the mutex when created and releases it when destroyed (RAII). This ensures that even if multiple threads log simultaneously, their messages won't interleave. The lock_guard provides exception safety - even if an exception occurs, the mutex is properly released.
private:
Logger() {
std::cout << "Logger initialized" << std::endl;
}
std::mutex mutex_;
};
Logger myLogger;. The only way to obtain a Logger is through getInstance(). The constructor prints a message to demonstrate that it's called exactly once, no matter how many times you call getInstance(). The private mutex_ member protects the log method from concurrent access issues.
int main() {
// Both references point to the SAME instance
Logger& logger1 = Logger::getInstance();
Logger& logger2 = Logger::getInstance();
logger1.log("First message");
logger2.log("Second message");
// Verify they're the same instance
std::cout << "Same instance? "
<< (&logger1 == &logger2 ? "Yes" : "No") << std::endl;
return 0;
}
// Output:
// Logger initialized
// [LOG] First message
// [LOG] Second message
// Same instance? Yes
logger1 and logger2 are references to the exact same object, proven by comparing their memory addresses. The "Logger initialized" message appears only once, confirming single instantiation. Both variables can call log(), and all log messages go to the same Logger instance. This is the Singleton pattern's core benefit - a globally accessible, single point of control.
The Factory Pattern
The Factory pattern provides an interface for creating objects without specifying their exact classes.
Instead of calling new ConcreteClass() directly, you ask a factory to create the object
for you. This decouples object creation from the code that uses the objects.
Think of a pizza shop: you don't go into the kitchen and make the pizza yourself. You tell the counter what type you want ("pepperoni" or "vegetarian"), and the shop (factory) creates the right pizza for you. The factory handles all the creation details.
#include <iostream>
#include <memory>
#include <string>
// Abstract product
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0;
virtual double area() const = 0;
};
draw() for rendering the shape and area() for calculating its area. This establishes a contract ensuring all shape types can be used interchangeably through the same interface. The virtual destructor ensures proper cleanup when deleting shapes through base class pointers.
// Concrete product - Circle
class Circle : public Shape {
public:
explicit Circle(double radius) : radius_(radius) {}
void draw() const override {
std::cout << "Drawing Circle with radius " << radius_ << std::endl;
}
double area() const override {
return 3.14159 * radius_ * radius_;
}
private:
double radius_;
};
explicit keyword on the constructor prevents implicit type conversions, ensuring circles are created intentionally. This class encapsulates all circle-specific behavior while conforming to the Shape contract, allowing it to be used polymorphically.
// Concrete product - Rectangle
class Rectangle : public Shape {
public:
Rectangle(double width, double height) : width_(width), height_(height) {}
void draw() const override {
std::cout << "Drawing Rectangle " << width_ << "x" << height_ << std::endl;
}
double area() const override {
return width_ * height_;
}
private:
double width_, height_;
};
// Factory class
class ShapeFactory {
public:
enum class ShapeType { Circle, Rectangle, Triangle };
static std::unique_ptr<Shape> createShape(ShapeType type,
double param1,
double param2 = 0) {
switch (type) {
case ShapeType::Circle:
return std::make_unique<Circle>(param1);
case ShapeType::Rectangle:
return std::make_unique<Rectangle>(param1, param2);
default:
return nullptr;
}
}
};
createShape() method uses a switch statement to create the appropriate concrete type, returning it as a std::unique_ptr<Shape> for automatic memory management. This decouples client code from concrete classes - you can add new shape types by modifying only the factory, not every place that creates shapes.
int main() {
// Client code doesn't need to know about concrete classes
auto circle = ShapeFactory::createShape(
ShapeFactory::ShapeType::Circle, 5.0);
auto rectangle = ShapeFactory::createShape(
ShapeFactory::ShapeType::Rectangle, 4.0, 6.0);
circle->draw(); // Drawing Circle with radius 5
rectangle->draw(); // Drawing Rectangle 4x6
std::cout << "Circle area: " << circle->area() << std::endl;
std::cout << "Rectangle area: " << rectangle->area() << std::endl;
return 0;
}
auto for type deduction since the factory returns smart pointers. This approach makes the code flexible and maintainable: adding a new Triangle shape only requires updating the factory, not rewriting client code. The pattern follows the Open/Closed Principle (open for extension, closed for modification).
The Builder Pattern
The Builder pattern separates the construction of a complex object from its representation. It's especially useful when an object has many optional parameters or requires multiple steps to create. Instead of a constructor with 10 parameters, you build the object step by step.
Think of ordering a custom computer: you specify the CPU, then RAM, then storage, then graphics card - each step is optional, and you can configure in any order. At the end, you call "build" and get your finished computer.
#include <iostream>
#include <string>
#include <optional>
class Computer {
public:
void showSpecs() const {
std::cout << "=== Computer Specs ===" << std::endl;
std::cout << "CPU: " << cpu_ << std::endl;
std::cout << "RAM: " << ram_gb_ << " GB" << std::endl;
std::cout << "Storage: " << storage_gb_ << " GB" << std::endl;
if (gpu_) {
std::cout << "GPU: " << *gpu_ << std::endl;
}
std::cout << "Has WiFi: " << (has_wifi_ ? "Yes" : "No") << std::endl;
}
private:
std::string cpu_ = "Unknown";
int ram_gb_ = 8;
int storage_gb_ = 256;
std::optional<std::string> gpu_;
bool has_wifi_ = false;
friend class ComputerBuilder; // Builder can access private members
};
std::optional). Instead of creating dozens of constructors for every possible combination
(Computer with GPU, Computer without GPU, Computer with WiFi, etc.), we use a Builder that allows setting
each field individually. The friend declaration grants the builder access to Computer's private
members, enabling direct field modification while keeping the Computer class itself immutable to outside code.
This approach eliminates constructor explosion and makes the code far more maintainable.
class ComputerBuilder {
public:
ComputerBuilder& setCPU(const std::string& cpu) {
computer_.cpu_ = cpu;
return *this; // Return *this for method chaining
}
ComputerBuilder& setRAM(int gb) {
computer_.ram_gb_ = gb;
return *this;
}
ComputerBuilder& setStorage(int gb) {
computer_.storage_gb_ = gb;
return *this;
}
ComputerBuilder& setGPU(const std::string& gpu) {
computer_.gpu_ = gpu;
return *this;
}
ComputerBuilder& enableWiFi() {
computer_.has_wifi_ = true;
return *this;
}
Computer build() {
return std::move(computer_);
}
private:
Computer computer_;
};
return *this;), which enables the elegant fluent interface pattern you see in the usage example.
This means you can chain multiple setter calls in a single statement like builder.setX().setY().setZ().
The build() method finalizes construction and returns the finished Computer object using
std::move for efficiency (avoiding unnecessary copies). This fluent style makes the code read
almost like natural language and clearly shows what's being configured.
int main() {
// Fluent interface - chain method calls
Computer gaming_pc = ComputerBuilder()
.setCPU("Intel i9-13900K")
.setRAM(32)
.setStorage(2000)
.setGPU("RTX 4090")
.enableWiFi()
.build();
Computer office_pc = ComputerBuilder()
.setCPU("Intel i5-13400")
.setRAM(16)
.setStorage(512)
.build();
gaming_pc.showSpecs();
std::cout << std::endl;
office_pc.showSpecs();
return 0;
}
Practice Questions: Creational Patterns
Problem: Create a Singleton class called ConfigManager that stores
application settings as key-value pairs (using std::map).
Requirements:
- Thread-safe getInstance() method
- Methods:
set(key, value)andget(key) - Return empty string if key not found
Show Solution
#include <iostream>
#include <string>
#include <map>
#include <mutex>
class ConfigManager {
public:
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
static ConfigManager& getInstance() {
static ConfigManager instance;
return instance;
}
void set(const std::string& key, const std::string& value) {
std::lock_guard<std::mutex> lock(mutex_);
config_[key] = value;
}
std::string get(const std::string& key) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = config_.find(key);
return (it != config_.end()) ? it->second : "";
}
private:
ConfigManager() = default;
std::map<std::string, std::string> config_;
mutable std::mutex mutex_;
};
int main() {
auto& config = ConfigManager::getInstance();
config.set("app_name", "MyApp");
config.set("version", "1.0.0");
std::cout << config.get("app_name") << std::endl; // MyApp
std::cout << config.get("missing") << std::endl; // (empty)
}
Problem: Implement a Factory pattern for creating different vehicle types.
Requirements:
- Abstract
Vehicleclass withdescribe()andwheels() - Concrete classes: Car (4 wheels), Motorcycle (2 wheels), Truck (6 wheels)
- VehicleFactory with
createVehicle(type)method
Show Solution
#include <iostream>
#include <memory>
#include <string>
class Vehicle {
public:
virtual ~Vehicle() = default;
virtual void describe() const = 0;
virtual int wheels() const = 0;
};
class Car : public Vehicle {
public:
void describe() const override {
std::cout << "Car - comfortable 4-door sedan" << std::endl;
}
int wheels() const override { return 4; }
};
class Motorcycle : public Vehicle {
public:
void describe() const override {
std::cout << "Motorcycle - fast two-wheeler" << std::endl;
}
int wheels() const override { return 2; }
};
class Truck : public Vehicle {
public:
void describe() const override {
std::cout << "Truck - heavy cargo vehicle" << std::endl;
}
int wheels() const override { return 6; }
};
class VehicleFactory {
public:
enum class Type { Car, Motorcycle, Truck };
static std::unique_ptr<Vehicle> createVehicle(Type type) {
switch (type) {
case Type::Car: return std::make_unique<Car>();
case Type::Motorcycle: return std::make_unique<Motorcycle>();
case Type::Truck: return std::make_unique<Truck>();
}
return nullptr;
}
};
int main() {
auto car = VehicleFactory::createVehicle(VehicleFactory::Type::Car);
auto bike = VehicleFactory::createVehicle(VehicleFactory::Type::Motorcycle);
car->describe(); // Car - comfortable 4-door sedan
bike->describe(); // Motorcycle - fast two-wheeler
std::cout << "Car wheels: " << car->wheels() << std::endl; // 4
}
Problem: Create an HTTPRequest class with a Builder for constructing HTTP requests.
Requirements:
- Fields: method (GET/POST), url, headers (map), body (optional)
- Builder with fluent interface
- Method
toString()to display the request
Show Solution
#include <iostream>
#include <string>
#include <map>
#include <optional>
#include <sstream>
class HTTPRequest {
public:
std::string toString() const {
std::ostringstream oss;
oss << method_ << " " << url_ << " HTTP/1.1\n";
for (const auto& [key, value] : headers_) {
oss << key << ": " << value << "\n";
}
if (body_) {
oss << "\n" << *body_;
}
return oss.str();
}
private:
std::string method_ = "GET";
std::string url_;
std::map<std::string, std::string> headers_;
std::optional<std::string> body_;
friend class HTTPRequestBuilder;
};
class HTTPRequestBuilder {
public:
HTTPRequestBuilder& setMethod(const std::string& method) {
request_.method_ = method;
return *this;
}
HTTPRequestBuilder& setURL(const std::string& url) {
request_.url_ = url;
return *this;
}
HTTPRequestBuilder& addHeader(const std::string& key,
const std::string& value) {
request_.headers_[key] = value;
return *this;
}
HTTPRequestBuilder& setBody(const std::string& body) {
request_.body_ = body;
return *this;
}
HTTPRequest build() { return std::move(request_); }
private:
HTTPRequest request_;
};
int main() {
HTTPRequest request = HTTPRequestBuilder()
.setMethod("POST")
.setURL("/api/users")
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer token123")
.setBody("{\"name\": \"John\", \"email\": \"john@example.com\"}")
.build();
std::cout << request.toString() << std::endl;
}
Problem: Implement a Builder pattern for creating documents in different formats.
Requirements:
- Document class with title, content, author, and date fields
- DocumentBuilder with fluent interface
- Support optional footer and header
- Method
render()to display formatted document
Show Solution
#include <iostream>
#include <string>
#include <optional>
class Document {
public:
void render() const {
if (header_) std::cout << "[" << *header_ << "]" << std::endl;
std::cout << "===============================" << std::endl;
std::cout << "Title: " << title_ << std::endl;
std::cout << "Author: " << author_ << std::endl;
std::cout << "Date: " << date_ << std::endl;
std::cout << "-------------------------------" << std::endl;
std::cout << content_ << std::endl;
std::cout << "===============================" << std::endl;
if (footer_) std::cout << "[" << *footer_ << "]" << std::endl;
}
private:
std::string title_;
std::string content_;
std::string author_;
std::string date_;
std::optional<std::string> header_;
std::optional<std::string> footer_;
friend class DocumentBuilder;
};
class DocumentBuilder {
public:
DocumentBuilder& setTitle(const std::string& title) {
doc_.title_ = title;
return *this;
}
DocumentBuilder& setContent(const std::string& content) {
doc_.content_ = content;
return *this;
}
DocumentBuilder& setAuthor(const std::string& author) {
doc_.author_ = author;
return *this;
}
DocumentBuilder& setDate(const std::string& date) {
doc_.date_ = date;
return *this;
}
DocumentBuilder& setHeader(const std::string& header) {
doc_.header_ = header;
return *this;
}
DocumentBuilder& setFooter(const std::string& footer) {
doc_.footer_ = footer;
return *this;
}
Document build() { return std::move(doc_); }
private:
Document doc_;
};
int main() {
Document report = DocumentBuilder()
.setTitle("Q4 Report")
.setAuthor("John Doe")
.setDate("2024-12-15")
.setContent("Sales increased by 25%...")
.setHeader("CONFIDENTIAL")
.setFooter("Page 1")
.build();
report.render();
}
Structural Patterns
Structural patterns deal with how classes and objects are composed to form larger structures. They help ensure that when one part of a system changes, the entire structure doesn't need to change.
The Adapter Pattern
The Adapter pattern allows incompatible interfaces to work together. Think of it like a power plug adapter when traveling - your laptop has one type of plug, but the wall outlet is different. The adapter sits in between and makes them compatible.
In software, you often need to use a class that has a different interface than what your code expects. Maybe you're integrating a third-party library, or working with legacy code. Instead of modifying the existing class (which might not be possible), you create an adapter that translates between interfaces.
#include <iostream>
#include <string>
// Legacy interface that we can't modify
class OldPrinter {
public:
void printOldWay(const std::string& text) {
std::cout << "*** OLD PRINTER ***" << std::endl;
std::cout << text << std::endl;
std::cout << "******************" << std::endl;
}
};
printOldWay() method has a different signature and behavior than what your modern code expects. This is a common scenario when integrating disparate systems or maintaining backward compatibility while modernizing your codebase.
// New interface that our system expects
class Printer {
public:
virtual ~Printer() = default;
virtual void print(const std::string& document) = 0;
};
Printer& parameters and calling the print() method. This interface represents your system's standard or convention. The goal of the Adapter pattern is to make the incompatible OldPrinter work through this interface without modifying either the OldPrinter class or rewriting all your existing code that depends on the Printer interface.
// Adapter - makes OldPrinter compatible with Printer interface
class PrinterAdapter : public Printer {
public:
PrinterAdapter(OldPrinter& oldPrinter) : oldPrinter_(oldPrinter) {}
void print(const std::string& document) override {
// Translate the call to the old interface
oldPrinter_.printOldWay(document);
}
private:
OldPrinter& oldPrinter_;
};
print() on the adapter, it translates that call to printOldWay() on the wrapped OldPrinter. This is composition-based adaptation (the adapter "has-a" OldPrinter). The adapter pattern lets you reuse existing code without modification, following the Open/Closed Principle - your code is open for extension but closed for modification.
// Client code that expects the new Printer interface
void printDocument(Printer& printer, const std::string& doc) {
printer.print(doc);
}
int main() {
OldPrinter legacyPrinter;
PrinterAdapter adapter(legacyPrinter);
// Now we can use the old printer through the new interface
printDocument(adapter, "Hello, Adapter Pattern!");
return 0;
}
printDocument() function expects a Printer interface and knows nothing about OldPrinter or adapters - it simply calls print(). We create an OldPrinter instance and wrap it in a PrinterAdapter. Now we can pass the adapter to printDocument(), and it works seamlessly! The adapter pattern allows legacy code and modern code to coexist peacefully. This is especially valuable in large systems where you're gradually migrating from old APIs to new ones without breaking existing functionality.
The Decorator Pattern
The Decorator pattern lets you add new behaviors to objects dynamically by wrapping them in decorator objects. Think of it like adding toppings to a pizza - you start with a base pizza and can add cheese, pepperoni, mushrooms, etc. Each topping "decorates" the pizza with extra features.
This pattern follows the Open/Closed Principle - you can extend behavior without modifying existing code. Instead of creating a subclass for every combination of features, you compose decorators at runtime.
#include <iostream>
#include <string>
#include <memory>
// Component interface
class Coffee {
public:
virtual ~Coffee() = default;
virtual std::string getDescription() const = 0;
virtual double getCost() const = 0;
};
// Concrete component - base coffee
class SimpleCoffee : public Coffee {
public:
std::string getDescription() const override {
return "Simple Coffee";
}
double getCost() const override {
return 2.00;
}
};
// Base decorator - also implements Coffee interface
class CoffeeDecorator : public Coffee {
public:
explicit CoffeeDecorator(std::unique_ptr<Coffee> coffee)
: coffee_(std::move(coffee)) {}
std::string getDescription() const override {
return coffee_->getDescription();
}
double getCost() const override {
return coffee_->getCost();
}
protected:
std::unique_ptr<Coffee> coffee_;
};
// Concrete decorators - add extras
class MilkDecorator : public CoffeeDecorator {
public:
explicit MilkDecorator(std::unique_ptr<Coffee> coffee)
: CoffeeDecorator(std::move(coffee)) {}
std::string getDescription() const override {
return coffee_->getDescription() + ", Milk";
}
double getCost() const override {
return coffee_->getCost() + 0.50;
}
};
class SugarDecorator : public CoffeeDecorator {
public:
explicit SugarDecorator(std::unique_ptr<Coffee> coffee)
: CoffeeDecorator(std::move(coffee)) {}
std::string getDescription() const override {
return coffee_->getDescription() + ", Sugar";
}
double getCost() const override {
return coffee_->getCost() + 0.25;
}
};
class WhippedCreamDecorator : public CoffeeDecorator {
public:
explicit WhippedCreamDecorator(std::unique_ptr<Coffee> coffee)
: CoffeeDecorator(std::move(coffee)) {}
std::string getDescription() const override {
return coffee_->getDescription() + ", Whipped Cream";
}
double getCost() const override {
return coffee_->getCost() + 0.75;
}
};
int main() {
// Start with simple coffee
std::unique_ptr<Coffee> myCoffee = std::make_unique<SimpleCoffee>();
std::cout << myCoffee->getDescription() << " - $"
<< myCoffee->getCost() << std::endl;
// Add milk
myCoffee = std::make_unique<MilkDecorator>(std::move(myCoffee));
std::cout << myCoffee->getDescription() << " - $"
<< myCoffee->getCost() << std::endl;
// Add sugar
myCoffee = std::make_unique<SugarDecorator>(std::move(myCoffee));
std::cout << myCoffee->getDescription() << " - $"
<< myCoffee->getCost() << std::endl;
// Add whipped cream
myCoffee = std::make_unique<WhippedCreamDecorator>(std::move(myCoffee));
std::cout << myCoffee->getDescription() << " - $"
<< myCoffee->getCost() << std::endl;
return 0;
}
// Output:
// Simple Coffee - $2
// Simple Coffee, Milk - $2.5
// Simple Coffee, Milk, Sugar - $2.75
// Simple Coffee, Milk, Sugar, Whipped Cream - $3.5
std::move to transfer ownership when wrapping - each decorator takes ownership of the previous coffee object. The variable myCoffee always holds a Coffee pointer, whether it's pointing to SimpleCoffee or a complex chain of decorators. This runtime flexibility is the Decorator pattern's superpower - you can create any combination of features without needing a separate class for "CoffeeWithMilkAndSugarAndWhippedCream". The pattern allows unlimited combinations through composition rather than inheritance.
The Facade Pattern
The Facade pattern provides a simplified interface to a complex subsystem. Think of it like using a TV remote - you don't need to understand the complex electronics inside, you just press "power" and "volume up." The remote is a facade that hides the complexity.
Facades are useful when you have a complex system with many classes and you want to provide a simple, easy-to-use interface for common tasks. The client doesn't need to know about all the subsystem classes - they just interact with the facade.
#include <iostream>
#include <string>
// Complex subsystem classes
class CPU {
public:
void freeze() { std::cout << "CPU: Freezing..." << std::endl; }
void jump(long address) {
std::cout << "CPU: Jumping to " << address << std::endl;
}
void execute() { std::cout << "CPU: Executing..." << std::endl; }
};
class Memory {
public:
void load(long address, const std::string& data) {
std::cout << "Memory: Loading '" << data
<< "' at " << address << std::endl;
}
};
class HardDrive {
public:
std::string read(long sector, int size) {
std::cout << "HardDrive: Reading sector " << sector << std::endl;
return "boot_data";
}
};
// Facade - provides simple interface to complex subsystem
class ComputerFacade {
public:
ComputerFacade() : cpu_(), memory_(), hardDrive_() {}
void start() {
std::cout << "=== Starting Computer ===" << std::endl;
cpu_.freeze();
std::string bootData = hardDrive_.read(0, 1024);
memory_.load(0, bootData);
cpu_.jump(0);
cpu_.execute();
std::cout << "=== Computer Started ===" << std::endl;
}
void shutdown() {
std::cout << "=== Shutting Down ===" << std::endl;
// Cleanup operations...
}
private:
CPU cpu_;
Memory memory_;
HardDrive hardDrive_;
};
start() and shutdown(). The facade internally manages the CPU, Memory, and HardDrive objects and orchestrates the correct sequence of operations. Client code no longer needs to know that booting requires freezing the CPU, reading from the hard drive, loading memory, jumping, and executing - they just call start(). This decouples clients from the subsystem's internal details. If the boot process changes (maybe you add a BIOS check or change the boot sequence), you only modify the facade, not every piece of client code. The Facade pattern reduces dependencies and provides a clean, high-level API for common operations.
start() and shutdown(). Compare this to directly using the subsystem classes, where you'd need to manage CPU, Memory, and HardDrive objects and remember the exact calling sequence. The facade acts as a "front desk" that handles all the coordination behind the scenes. This makes the code more maintainable (changes to the subsystem don't affect clients), more testable (you can mock the facade instead of all subsystem classes), and more understandable (the high-level intent is clear). Facades are especially valuable when integrating third-party libraries or legacy systems with complex APIs - you create a simple facade that exposes only the functionality you need.
Practice Questions: Structural Patterns
Problem: You have a legacy CelsiusThermometer class but your
new system expects a Thermometer interface with getTemperatureF().
Given:
class CelsiusThermometer {
public:
double getTemperatureC() { return 25.0; }
};
Task: Create an adapter that makes CelsiusThermometer work with code expecting Fahrenheit.
Show Solution
class Thermometer {
public:
virtual ~Thermometer() = default;
virtual double getTemperatureF() = 0;
};
class ThermometerAdapter : public Thermometer {
public:
ThermometerAdapter(CelsiusThermometer& t) : celsius_(t) {}
double getTemperatureF() override {
return celsius_.getTemperatureC() * 9.0 / 5.0 + 32.0;
}
private:
CelsiusThermometer& celsius_;
};
int main() {
CelsiusThermometer oldThermo;
ThermometerAdapter adapter(oldThermo);
std::cout << adapter.getTemperatureF() << "°F" << std::endl; // 77°F
}
Problem: Create a Pizza decorator system with base pizza and toppings.
Requirements:
- Base
Pizzainterface withgetDescription()andgetPrice() PlainPizza- $8.00- Decorators: Cheese (+$1.50), Pepperoni (+$2.00), Mushrooms (+$1.25)
Show Solution
#include <iostream>
#include <memory>
class Pizza {
public:
virtual ~Pizza() = default;
virtual std::string getDescription() const = 0;
virtual double getPrice() const = 0;
};
class PlainPizza : public Pizza {
public:
std::string getDescription() const override { return "Plain Pizza"; }
double getPrice() const override { return 8.00; }
};
class ToppingDecorator : public Pizza {
public:
ToppingDecorator(std::unique_ptr<Pizza> p) : pizza_(std::move(p)) {}
protected:
std::unique_ptr<Pizza> pizza_;
};
class CheeseDecorator : public ToppingDecorator {
public:
using ToppingDecorator::ToppingDecorator;
std::string getDescription() const override {
return pizza_->getDescription() + ", Extra Cheese";
}
double getPrice() const override { return pizza_->getPrice() + 1.50; }
};
class PepperoniDecorator : public ToppingDecorator {
public:
using ToppingDecorator::ToppingDecorator;
std::string getDescription() const override {
return pizza_->getDescription() + ", Pepperoni";
}
double getPrice() const override { return pizza_->getPrice() + 2.00; }
};
int main() {
std::unique_ptr<Pizza> pizza = std::make_unique<PlainPizza>();
pizza = std::make_unique<CheeseDecorator>(std::move(pizza));
pizza = std::make_unique<PepperoniDecorator>(std::move(pizza));
std::cout << pizza->getDescription() << std::endl;
std::cout << "$" << pizza->getPrice() << std::endl;
// Plain Pizza, Extra Cheese, Pepperoni - $11.50
}
Problem: Create a Facade pattern for a complex home theater system.
Requirements:
- Subsystem classes:
TV,SoundSystem,StreamingPlayer,Lights - Each subsystem has on/off methods and specific operations
- HomeTheaterFacade with
watchMovie()andendMovie()
Show Solution
#include <iostream>
#include <string>
class TV {
public:
void on() { std::cout << "TV: Turning on" << std::endl; }
void off() { std::cout << "TV: Turning off" << std::endl; }
void setInput(const std::string& input) {
std::cout << "TV: Setting input to " << input << std::endl;
}
};
class SoundSystem {
public:
void on() { std::cout << "Sound: Powering on" << std::endl; }
void off() { std::cout << "Sound: Powering off" << std::endl; }
void setVolume(int level) {
std::cout << "Sound: Volume set to " << level << std::endl;
}
void setSurroundMode() {
std::cout << "Sound: Surround mode enabled" << std::endl;
}
};
class StreamingPlayer {
public:
void on() { std::cout << "Player: Starting up" << std::endl; }
void off() { std::cout << "Player: Shutting down" << std::endl; }
void play(const std::string& movie) {
std::cout << "Player: Playing '" << movie << "'" << std::endl;
}
void stop() { std::cout << "Player: Stopped" << std::endl; }
};
class Lights {
public:
void dim(int level) {
std::cout << "Lights: Dimming to " << level << "%" << std::endl;
}
void on() { std::cout << "Lights: Full brightness" << std::endl; }
};
class HomeTheaterFacade {
public:
void watchMovie(const std::string& movie) {
std::cout << "=== Getting ready to watch " << movie << " ===" << std::endl;
lights_.dim(10);
tv_.on();
tv_.setInput("HDMI1");
sound_.on();
sound_.setVolume(25);
sound_.setSurroundMode();
player_.on();
player_.play(movie);
std::cout << "=== Movie started! Enjoy! ===" << std::endl;
}
void endMovie() {
std::cout << "=== Shutting down theater ===" << std::endl;
player_.stop();
player_.off();
sound_.off();
tv_.off();
lights_.on();
std::cout << "=== Theater shut down ===" << std::endl;
}
private:
TV tv_;
SoundSystem sound_;
StreamingPlayer player_;
Lights lights_;
};
int main() {
HomeTheaterFacade theater;
theater.watchMovie("Inception");
// ... watch movie ...
theater.endMovie();
}
Problem: Adapt legacy audio players to work with a modern MediaPlayer interface.
Requirements:
- Legacy classes:
VLCPlayerwithplayVLC(),MP4PlayerwithplayMP4() - Modern interface:
MediaPlayerwithplay(filename) - MediaAdapter that auto-detects format and uses appropriate player
Show Solution
#include <iostream>
#include <string>
#include <memory>
// Legacy players
class VLCPlayer {
public:
void playVLC(const std::string& filename) {
std::cout << "VLC: Playing " << filename << std::endl;
}
};
class MP4Player {
public:
void playMP4(const std::string& filename) {
std::cout << "MP4 Player: Playing " << filename << std::endl;
}
};
// Modern interface
class MediaPlayer {
public:
virtual ~MediaPlayer() = default;
virtual void play(const std::string& filename) = 0;
};
// Adapter
class MediaAdapter : public MediaPlayer {
public:
void play(const std::string& filename) override {
std::string ext = filename.substr(filename.find_last_of('.') + 1);
if (ext == "vlc") {
vlcPlayer_.playVLC(filename);
} else if (ext == "mp4") {
mp4Player_.playMP4(filename);
} else {
std::cout << "Unsupported format: " << ext << std::endl;
}
}
private:
VLCPlayer vlcPlayer_;
MP4Player mp4Player_;
};
// Client
class AudioPlayer : public MediaPlayer {
public:
void play(const std::string& filename) override {
std::string ext = filename.substr(filename.find_last_of('.') + 1);
if (ext == "mp3") {
std::cout << "AudioPlayer: Playing " << filename << std::endl;
} else {
adapter_.play(filename);
}
}
private:
MediaAdapter adapter_;
};
int main() {
AudioPlayer player;
player.play("song.mp3"); // Native support
player.play("video.mp4"); // Via adapter
player.play("stream.vlc"); // Via adapter
}
Behavioral Patterns
Behavioral patterns are concerned with communication between objects, how they operate together, and how responsibilities are distributed among them.
The Observer Pattern
The Observer pattern defines a one-to-many dependency between objects. When one object (the "subject") changes state, all its dependents ("observers") are notified automatically. Think of it like a newsletter subscription - when a new article is published, all subscribers get notified.
This pattern is fundamental to event-driven programming. It's used extensively in GUI frameworks (button click handlers), reactive programming, and the Model-View-Controller (MVC) architecture.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
// Observer interface
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& message) = 0;
};
update() method that observers implement to receive notifications. This decouples the subject from concrete observer implementations - the subject only knows about the Observer interface, not specific observer types. The virtual destructor ensures proper cleanup when deleting observers through base class pointers. This interface-based design allows any class to become an observer by simply implementing the update method, making the pattern extremely flexible.
// Subject (Observable) class
class NewsAgency {
public:
void subscribe(Observer* observer) {
observers_.push_back(observer);
}
subscribe() method adds an observer to the notification list. The subject maintains a collection of observer pointers, allowing multiple observers to register interest in updates. This creates a one-to-many relationship where one subject can notify many observers. The observers list is dynamically managed - observers can be added or removed at runtime. This flexibility is key to the Observer pattern's power, enabling loose coupling between the subject and its observers.
void unsubscribe(Observer* observer) {
observers_.erase(
std::remove(observers_.begin(), observers_.end(), observer),
observers_.end()
);
}
unsubscribe() method removes an observer from the notification list using the erase-remove idiom. This two-step process first uses std::remove to move the matching observer to the end, then erase to actually remove it from the vector. This allows observers to dynamically opt-out of notifications, giving them control over when they want to stop listening. Proper unsubscription is crucial for memory management and preventing notifications to destroyed observers.
void publishNews(const std::string& news) {
std::cout << "News Agency publishing: " << news << std::endl;
notifyAll(news);
}
publishNews() method is the subject's state-changing operation that triggers notifications. When news is published, the subject doesn't directly notify observers - instead, it delegates to the private notifyAll() method. This separation of concerns keeps the public API clean and allows internal notification logic to be modified without affecting the public interface. The method represents the "change" in the observer pattern that causes all registered observers to be updated.
private:
void notifyAll(const std::string& message) {
for (Observer* observer : observers_) {
observer->update(message);
}
}
std::vector<Observer*> observers_;
};
notifyAll() method iterates through all registered observers and calls their update() methods, passing the news message. This is where the "push" notification happens - the subject actively pushes data to all observers. The method doesn't care what each observer does with the notification; it simply ensures everyone gets informed. The observers vector stores raw pointers because the subject doesn't own the observers - they have independent lifetimes managed elsewhere.
// Concrete observers
class NewsChannel : public Observer {
public:
NewsChannel(const std::string& name) : name_(name) {}
void update(const std::string& message) override {
std::cout << name_ << " received: " << message << std::endl;
}
private:
std::string name_;
};
update() method by printing the received message to simulate broadcasting on television. Each observer can have its own state (like the channel name) and react to updates in its own way. The key is that the NewsChannel doesn't need to know anything about the NewsAgency's internal structure - it only needs to implement the Observer interface. This decoupling allows you to add new observer types without modifying the subject.
class NewsApp : public Observer {
public:
NewsApp(const std::string& name) : name_(name) {}
void update(const std::string& message) override {
std::cout << name_ << " notification: " << message << std::endl;
}
private:
std::string name_;
};
int main() {
NewsAgency agency;
NewsChannel cnn("CNN");
NewsChannel bbc("BBC");
NewsApp mobileApp("NewsApp");
// Subscribe to news
agency.subscribe(&cnn);
agency.subscribe(&bbc);
agency.subscribe(&mobileApp);
// Publish news - all observers notified
agency.publishNews("Breaking: C++ 26 features announced!");
std::cout << "\n--- BBC unsubscribes ---\n" << std::endl;
agency.unsubscribe(&bbc);
// Only CNN and NewsApp receive this
agency.publishNews("Tech stocks rally on AI news");
return 0;
}
// Output:
// News Agency publishing: Breaking: C++ 26 features announced!
// CNN received: Breaking: C++ 26 features announced!
// BBC received: Breaking: C++ 26 features announced!
// NewsApp notification: Breaking: C++ 26 features announced!
//
// --- BBC unsubscribes ---
//
// News Agency publishing: Tech stocks rally on AI news
// CNN received: Tech stocks rally on AI news
// NewsApp notification: Tech stocks rally on AI news
publishNews() is called, all subscribed observers receive updates without the NewsAgency needing to know their concrete types. The output shows that CNN, BBC, and NewsApp all receive the first announcement. After BBC unsubscribes, only CNN and NewsApp receive the second announcement, demonstrating dynamic subscription management. This pattern is foundational for GUI event handling, reactive programming frameworks, and Model-View-Controller architectures where views automatically update when the model changes.
The Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from the clients that use it. Think of it like choosing a payment method at checkout - you can pay with credit card, PayPal, or cash. Each is a different "strategy" for payment.
This pattern is great when you have multiple ways to perform an operation and want to switch between them at runtime. It follows the Open/Closed Principle - you can add new strategies without modifying existing code.
#include <iostream>
#include <memory>
#include <vector>
#include <algorithm>
// Strategy interface
class SortStrategy {
public:
virtual ~SortStrategy() = default;
virtual void sort(std::vector<int>& data) = 0;
virtual std::string getName() const = 0;
};
sort() to perform the algorithm and getName() for identification. This abstraction allows the context class to work with any sorting algorithm without knowing implementation details. The interface follows the Dependency Inversion Principle - high-level code depends on the abstraction, not concrete implementations. Adding new sorting algorithms requires only creating a new class that implements this interface, without modifying existing code.
// Concrete strategies
class BubbleSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
for (size_t i = 0; i < data.size(); ++i) {
for (size_t j = 0; j < data.size() - i - 1; ++j) {
if (data[j] > data[j + 1]) {
std::swap(data[j], data[j + 1]);
}
}
}
}
std::string getName() const override { return "Bubble Sort"; }
};
class QuickSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
quickSort(data, 0, data.size() - 1);
}
std::string getName() const override { return "Quick Sort"; }
private:
void quickSort(std::vector<int>& arr, int low, int high) {
if (low < high) {
int pivot = partition(arr, low, high);
quickSort(arr, low, pivot - 1);
quickSort(arr, pivot + 1, high);
}
}
int partition(std::vector<int>& arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; ++j) {
if (arr[j] <= pivot) {
++i;
std::swap(arr[i], arr[j]);
}
}
std::swap(arr[i + 1], arr[high]);
return i + 1;
}
};
partition() and recursive quickSort() - as private implementation details. The context class doesn't need to know about these internals; it only calls the public sort() method. This encapsulation is a key benefit of the Strategy pattern - each algorithm is self-contained and can be as simple or complex as needed.
class STLSort : public SortStrategy {
public:
void sort(std::vector<int>& data) override {
std::sort(data.begin(), data.end());
}
std::string getName() const override { return "STL Sort"; }
};
// Context class that uses the strategy
class Sorter {
public:
void setStrategy(std::unique_ptr<SortStrategy> strategy) {
strategy_ = std::move(strategy);
}
void performSort(std::vector<int>& data) {
if (strategy_) {
std::cout << "Using " << strategy_->getName() << std::endl;
strategy_->sort(data);
}
}
private:
std::unique_ptr<SortStrategy> strategy_;
};
performSort(). The context doesn't implement sorting logic itself - it relies entirely on the injected strategy. The setStrategy() method allows runtime strategy switching, which is the pattern's key feature. The context uses std::unique_ptr for automatic memory management, taking ownership of the strategy. This design separates the algorithm selection (context's responsibility) from algorithm implementation (strategy's responsibility).
void printVector(const std::vector<int>& v) {
for (int n : v) std::cout << n << " ";
std::cout << std::endl;
}
int main() {
Sorter sorter;
std::vector<int> data = {64, 34, 25, 12, 22, 11, 90};
std::cout << "Original: ";
printVector(data);
// Use Bubble Sort
sorter.setStrategy(std::make_unique<BubbleSort>());
auto copy1 = data;
sorter.performSort(copy1);
std::cout << "Result: ";
printVector(copy1);
// Switch to Quick Sort at runtime
sorter.setStrategy(std::make_unique<QuickSort>());
auto copy2 = data;
sorter.performSort(copy2);
std::cout << "Result: ";
printVector(copy2);
return 0;
}
setStrategy(). The client code remains clean and doesn't contain any sorting logic. You can easily add new strategies (like MergeSort or HeapSort) without modifying the Sorter class or main function. This pattern is widely used in game AI (different enemy behaviors), payment processing (different payment methods), data validation (different validation rules), and compression (different compression algorithms). The strategy pattern provides algorithmic flexibility with clean separation of concerns.
The Command Pattern
The Command pattern encapsulates a request as an object, letting you parameterize clients with different requests, queue requests, and support undoable operations. Think of it like a restaurant order ticket - the waiter writes down your order (command), passes it to the kitchen (invoker), which executes it. The order can be modified, cancelled, or kept for records.
This pattern is essential for implementing undo/redo functionality, transaction systems, macro recording, and command queuing. Each command knows how to execute itself and optionally how to undo itself.
#include <iostream>
#include <memory>
#include <vector>
#include <stack>
#include <string>
// Receiver - the actual object that performs the work
class TextEditor {
public:
void insertText(const std::string& text, size_t position) {
content_.insert(position, text);
}
void deleteText(size_t position, size_t length) {
content_.erase(position, length);
}
std::string getContent() const { return content_; }
size_t getLength() const { return content_.length(); }
private:
std::string content_;
};
insertText() and deleteText() that manipulate the document content. The receiver doesn't know anything about commands; it just provides operations. This separation is key to the Command pattern: the receiver focuses on business logic (text editing), while commands handle invocation, queuing, and undo/redo. Multiple commands can call the same receiver methods in different combinations to create complex operations.
// Command interface
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
execute() (to perform the action) and undo() (to reverse it). This interface is the core of the pattern - it allows treating all commands uniformly, regardless of what they do. Commands are first-class objects that encapsulate a request as a self-contained object. This enables powerful features like command history, macro recording, transaction rollback, and distributed command execution. The pattern decouples the sender (who triggers the command) from the receiver (who performs the work).
// Concrete commands
class InsertCommand : public Command {
public:
InsertCommand(TextEditor& editor, const std::string& text, size_t pos)
: editor_(editor), text_(text), position_(pos) {}
void execute() override {
editor_.insertText(text_, position_);
}
void undo() override {
editor_.deleteText(position_, text_.length());
}
private:
TextEditor& editor_;
std::string text_;
size_t position_;
};
execute() method calls the receiver's insertText(), while undo() reverses it by deleting the inserted text. Notice how the command stores all necessary state - this makes it self-contained and reusable. You could store these command objects in a queue, execute them later, replay them, or send them across a network. This is the command pattern's power: turning method calls into objects.
class DeleteCommand : public Command {
public:
DeleteCommand(TextEditor& editor, size_t pos, size_t len)
: editor_(editor), position_(pos), length_(len) {
// Save deleted text for undo
deletedText_ = editor_.getContent().substr(pos, len);
}
void execute() override {
editor_.deleteText(position_, length_);
}
void undo() override {
editor_.insertText(deletedText_, position_);
}
private:
TextEditor& editor_;
size_t position_;
size_t length_;
std::string deletedText_;
};
// Invoker - manages command execution and history
class CommandManager {
public:
void executeCommand(std::unique_ptr<Command> cmd) {
cmd->execute();
history_.push(std::move(cmd));
// Clear redo stack when new command is executed
while (!redoStack_.empty()) redoStack_.pop();
}
executeCommand() method calls the command's execute() and pushes it onto the history stack for potential undo. Crucially, it clears the redo stack when a new command is executed - this is standard undo/redo behavior (after typing new text, you can't redo the text you previously deleted). The invoker doesn't know what commands do; it only knows they have execute() and undo() methods. This separation allows adding new command types without modifying the invoker.
void undo() {
if (!history_.empty()) {
auto cmd = std::move(history_.top());
history_.pop();
cmd->undo();
redoStack_.push(std::move(cmd));
}
}
undo() method pops the most recent command from history, calls its undo() method, and moves it to the redo stack. This allows undoing the last operation and potentially redoing it later. The method uses std::unique_ptr for automatic memory management - when a command is moved from one stack to another, ownership transfers cleanly. The pattern elegantly handles arbitrary undo depth - the history stack can hold any number of commands, limited only by memory.
void redo() {
if (!redoStack_.empty()) {
auto cmd = std::move(redoStack_.top());
redoStack_.pop();
cmd->execute();
history_.push(std::move(cmd));
}
}
private:
std::stack<std::unique_ptr<Command>> history_;
std::stack<std::unique_ptr<Command>> redoStack_;
};
redo() method reverses an undo by popping from the redo stack, executing the command again, and pushing it back to history. This creates the classic undo/redo cycle found in text editors and other applications. The two stacks (history and redo) work together to enable bidirectional navigation through command history. The Command pattern makes implementing this feature straightforward - without it, you'd need complex state tracking for every possible operation. Commands encapsulate all the logic for both doing and undoing actions.
int main() {
TextEditor editor;
CommandManager manager;
// Execute commands
manager.executeCommand(
std::make_unique<InsertCommand>(editor, "Hello", 0));
std::cout << "After insert: " << editor.getContent() << std::endl;
manager.executeCommand(
std::make_unique<InsertCommand>(editor, " World", 5));
std::cout << "After insert: " << editor.getContent() << std::endl;
// Undo
manager.undo();
std::cout << "After undo: " << editor.getContent() << std::endl;
// Redo
manager.redo();
std::cout << "After redo: " << editor.getContent() << std::endl;
return 0;
}
// Output:
// After insert: Hello
// After insert: Hello World
// After undo: Hello
// After redo: Hello World
undo() reverses the last operation (removing " World"), while redo() reapplies it. The pattern's power lies in its flexibility: you could add a "save all commands to file" feature for macro recording, implement multi-level undo (just keep more history), add command validation before execution, queue commands for batch processing, or implement transactional behavior where a group of commands can be rolled back as a unit. The Command pattern is fundamental to many professional applications.
Practice Questions: Behavioral Patterns
Problem: Create a WeatherStation (subject) that notifies displays (observers) when temperature changes.
Requirements:
- WeatherStation with
setTemperature(double) - Display observers that print "Temperature updated to X°C"
- Support multiple displays
Show Solution
#include <iostream>
#include <vector>
#include <string>
class WeatherObserver {
public:
virtual void update(double temp) = 0;
virtual ~WeatherObserver() = default;
};
class WeatherStation {
public:
void subscribe(WeatherObserver* obs) { observers_.push_back(obs); }
void setTemperature(double temp) {
temperature_ = temp;
for (auto* obs : observers_) {
obs->update(temp);
}
}
private:
double temperature_ = 0;
std::vector<WeatherObserver*> observers_;
};
class Display : public WeatherObserver {
public:
Display(const std::string& name) : name_(name) {}
void update(double temp) override {
std::cout << name_ << ": Temperature updated to "
<< temp << "°C" << std::endl;
}
private:
std::string name_;
};
int main() {
WeatherStation station;
Display phone("Phone");
Display tv("TV");
station.subscribe(&phone);
station.subscribe(&tv);
station.setTemperature(25.5);
// Phone: Temperature updated to 25.5°C
// TV: Temperature updated to 25.5°C
}
Problem: Implement payment processing with different strategies.
Requirements:
- PaymentStrategy interface with
pay(double amount) - CreditCard, PayPal, and Crypto payment strategies
- ShoppingCart that uses the selected strategy
Show Solution
#include <iostream>
#include <memory>
class PaymentStrategy {
public:
virtual ~PaymentStrategy() = default;
virtual void pay(double amount) = 0;
};
class CreditCard : public PaymentStrategy {
public:
CreditCard(const std::string& num) : cardNumber_(num) {}
void pay(double amount) override {
std::cout << "Paid $" << amount << " with Credit Card "
<< cardNumber_.substr(cardNumber_.length()-4) << std::endl;
}
private:
std::string cardNumber_;
};
class PayPal : public PaymentStrategy {
public:
PayPal(const std::string& email) : email_(email) {}
void pay(double amount) override {
std::cout << "Paid $" << amount << " via PayPal ("
<< email_ << ")" << std::endl;
}
private:
std::string email_;
};
class ShoppingCart {
public:
void setPaymentMethod(std::unique_ptr<PaymentStrategy> method) {
payment_ = std::move(method);
}
void checkout(double total) {
if (payment_) payment_->pay(total);
}
private:
std::unique_ptr<PaymentStrategy> payment_;
};
int main() {
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCard>("1234567890123456"));
cart.checkout(99.99); // Paid $99.99 with Credit Card 3456
cart.setPaymentMethod(std::make_unique<PayPal>("user@email.com"));
cart.checkout(49.99); // Paid $49.99 via PayPal
}
Problem: Create a calculator that supports undo/redo operations.
Requirements:
- Calculator with current value
- Commands: Add, Subtract, Multiply, Divide
- Support undo() and redo() operations
- Each command stores the operand for undo
Show Solution
#include <iostream>
#include <stack>
#include <memory>
class Calculator {
public:
double getValue() const { return value_; }
void setValue(double v) { value_ = v; }
private:
double value_ = 0;
};
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
class AddCommand : public Command {
public:
AddCommand(Calculator& calc, double value)
: calc_(calc), value_(value) {}
void execute() override { calc_.setValue(calc_.getValue() + value_); }
void undo() override { calc_.setValue(calc_.getValue() - value_); }
private:
Calculator& calc_;
double value_;
};
class SubtractCommand : public Command {
public:
SubtractCommand(Calculator& calc, double value)
: calc_(calc), value_(value) {}
void execute() override { calc_.setValue(calc_.getValue() - value_); }
void undo() override { calc_.setValue(calc_.getValue() + value_); }
private:
Calculator& calc_;
double value_;
};
class MultiplyCommand : public Command {
public:
MultiplyCommand(Calculator& calc, double value)
: calc_(calc), value_(value) {}
void execute() override { calc_.setValue(calc_.getValue() * value_); }
void undo() override { calc_.setValue(calc_.getValue() / value_); }
private:
Calculator& calc_;
double value_;
};
class CalculatorInvoker {
public:
void executeCommand(std::unique_ptr<Command> cmd) {
cmd->execute();
history_.push(std::move(cmd));
while (!redoStack_.empty()) redoStack_.pop();
}
void undo() {
if (history_.empty()) return;
auto cmd = std::move(history_.top());
history_.pop();
cmd->undo();
redoStack_.push(std::move(cmd));
}
void redo() {
if (redoStack_.empty()) return;
auto cmd = std::move(redoStack_.top());
redoStack_.pop();
cmd->execute();
history_.push(std::move(cmd));
}
private:
std::stack<std::unique_ptr<Command>> history_;
std::stack<std::unique_ptr<Command>> redoStack_;
};
int main() {
Calculator calc;
CalculatorInvoker invoker;
invoker.executeCommand(std::make_unique<AddCommand>(calc, 10));
std::cout << "After +10: " << calc.getValue() << std::endl; // 10
invoker.executeCommand(std::make_unique<MultiplyCommand>(calc, 5));
std::cout << "After *5: " << calc.getValue() << std::endl; // 50
invoker.undo();
std::cout << "After undo: " << calc.getValue() << std::endl; // 10
invoker.redo();
std::cout << "After redo: " << calc.getValue() << std::endl; // 50
}
Problem: Implement a stock price notification system using Observer pattern.
Requirements:
- Stock class with symbol and price
- Investors as observers who get notified of price changes
- Support
subscribe(),unsubscribe(), andnotify() - Investors should see "Stock X changed from Y to Z"
Show Solution
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
class StockObserver {
public:
virtual ~StockObserver() = default;
virtual void update(const std::string& symbol,
double oldPrice, double newPrice) = 0;
};
class Stock {
public:
Stock(const std::string& symbol, double price)
: symbol_(symbol), price_(price) {}
void subscribe(StockObserver* obs) {
observers_.push_back(obs);
}
void unsubscribe(StockObserver* obs) {
observers_.erase(
std::remove(observers_.begin(), observers_.end(), obs),
observers_.end());
}
void setPrice(double newPrice) {
double oldPrice = price_;
price_ = newPrice;
notify(oldPrice, newPrice);
}
double getPrice() const { return price_; }
std::string getSymbol() const { return symbol_; }
private:
void notify(double oldPrice, double newPrice) {
for (auto* obs : observers_) {
obs->update(symbol_, oldPrice, newPrice);
}
}
std::string symbol_;
double price_;
std::vector<StockObserver*> observers_;
};
class Investor : public StockObserver {
public:
Investor(const std::string& name) : name_(name) {}
void update(const std::string& symbol,
double oldPrice, double newPrice) override {
std::cout << name_ << ": Stock " << symbol
<< " changed from $" << oldPrice
<< " to $" << newPrice;
if (newPrice > oldPrice) {
std::cout << " (+" << (newPrice - oldPrice) << ")";
} else {
std::cout << " (" << (newPrice - oldPrice) << ")";
}
std::cout << std::endl;
}
private:
std::string name_;
};
int main() {
Stock apple("AAPL", 150.0);
Investor john("John");
Investor jane("Jane");
apple.subscribe(&john);
apple.subscribe(&jane);
apple.setPrice(155.0); // Both notified
apple.setPrice(148.0); // Both notified
apple.unsubscribe(&jane);
apple.setPrice(160.0); // Only John notified
}
Modern C++ Idioms
Modern C++ introduces powerful idioms that go beyond classic GoF patterns. These idioms leverage C++'s unique features like templates, RAII, and compile-time programming.
RAII - Resource Acquisition Is Initialization
RAII is perhaps the most important C++ idiom. The idea is simple: tie the lifecycle of a resource (memory, file handle, mutex lock, network connection) to the lifetime of an object. When the object is created, it acquires the resource. When the object is destroyed, it releases the resource.
This pattern eliminates resource leaks because C++ guarantees that destructors are called when objects go out of scope, even when exceptions are thrown. Smart pointers (unique_ptr, shared_ptr) are the most common examples of RAII in modern C++.
#include <iostream>
#include <fstream>
#include <mutex>
#include <memory>
// Custom RAII wrapper for file handling
class FileHandle {
public:
explicit FileHandle(const std::string& filename)
: file_(filename) {
if (!file_.is_open()) {
throw std::runtime_error("Failed to open file: " + filename);
}
std::cout << "File opened: " << filename << std::endl;
}
~FileHandle() {
if (file_.is_open()) {
file_.close();
std::cout << "File closed automatically" << std::endl;
}
}
// Delete copy operations
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// Allow move operations
FileHandle(FileHandle&& other) noexcept : file_(std::move(other.file_)) {}
void write(const std::string& data) {
file_ << data;
}
private:
std::ofstream file_;
};
write() method provides the public interface for using the resource. The private file_ member is the actual resource being managed. This design enforces unique ownership semantics - exactly one FileHandle owns the file at any time, preventing resource management conflicts.
// RAII mutex lock
class LockGuard {
public:
explicit LockGuard(std::mutex& mtx) : mutex_(mtx) {
mutex_.lock();
std::cout << "Mutex locked" << std::endl;
}
~LockGuard() {
mutex_.unlock();
std::cout << "Mutex unlocked" << std::endl;
}
LockGuard(const LockGuard&) = delete;
LockGuard& operator=(const LockGuard&) = delete;
private:
std::mutex& mutex_;
};
std::lock_guard does in the standard library. The pattern guarantees exception-safe locking - if your code throws an exception while holding the lock, the destructor still runs and releases it.
void processWithLock(std::mutex& mtx) {
LockGuard lock(mtx); // Automatically locks
// Do work...
std::cout << "Processing..." << std::endl;
// If exception thrown here, destructor still runs!
// throw std::runtime_error("Error!");
} // Automatically unlocks when lock goes out of scope
lock is constructed and released when it goes out of scope at the closing brace. Even if an exception is thrown in the middle of processing, the destructor runs and unlocks the mutex. No manual unlock calls needed, no try/finally blocks, no possibility of forgetting to unlock. The scope-based lifetime makes the critical section boundaries visually clear. This pattern is fundamental to writing exception-safe multithreaded code in C++.
int main() {
// Smart pointers are RAII for dynamic memory
{
auto ptr = std::make_unique<int>(42);
std::cout << "Value: " << *ptr << std::endl;
} // Memory automatically freed here
// File RAII
try {
FileHandle file("output.txt");
file.write("Hello, RAII!\n");
// File automatically closed when 'file' goes out of scope
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
// Mutex RAII
std::mutex mtx;
processWithLock(mtx);
return 0;
}
std::unique_ptr, std::lock_guard, std::fstream, etc.
CRTP - Curiously Recurring Template Pattern
CRTP is a template technique where a class inherits from a template instantiated with itself. It sounds mind-bending, but it's incredibly powerful for adding functionality to classes at compile time without the overhead of virtual functions.
Common uses include: implementing the Singleton pattern, adding comparison operators, implementing object counting, and creating compile-time polymorphism (static polymorphism).
#include <iostream>
// CRTP base - provides functionality to derived classes
template<typename Derived>
class Counter {
public:
Counter() { ++count_; }
Counter(const Counter&) { ++count_; }
~Counter() { --count_; }
static int getCount() { return count_; }
private:
static int count_;
};
template<typename Derived>
int Counter<Derived>::count_ = 0;
Derived - each different type that inherits from Counter gets its own static counter variable. When you write Counter<Dog> and Counter<Cat>, the compiler generates two completely separate Counter classes with separate static count_ variables. The constructors increment the count, the destructor decrements it, tracking how many instances of each specific type exist. This compile-time polymorphism has zero runtime overhead compared to virtual functions.
// Classes using CRTP - each gets its own counter!
class Dog : public Counter<Dog> {
public:
Dog(const std::string& name) : name_(name) {}
std::string name_;
};
class Cat : public Counter<Cat> {
public:
Cat(const std::string& name) : name_(name) {}
std::string name_;
};
int main() {
Dog d1("Rex"), d2("Buddy"), d3("Max");
Cat c1("Whiskers"), c2("Mittens");
std::cout << "Dogs: " << Dog::getCount() << std::endl; // 3
std::cout << "Cats: " << Cat::getCount() << std::endl; // 2
{
Dog temp("Temp");
std::cout << "Dogs (with temp): " << Dog::getCount() << std::endl; // 4
}
std::cout << "Dogs (after temp): " << Dog::getCount() << std::endl; // 3
return 0;
}
Counter<Dog> and Counter<Cat> respectively. The counters track object lifetimes automatically - when objects are created, counts increase; when they're destroyed (like temp going out of scope), counts decrease. Each class has its own separate counter maintained by the template system. CRTP provides this functionality without runtime polymorphism overhead - no virtual functions, no vtable lookups, everything resolved at compile time. This pattern is used throughout the C++ standard library for policies and mixins.
// CRTP for static polymorphism - no virtual function overhead!
template<typename Derived>
class Shape {
public:
void draw() {
// Call derived class implementation without virtual
static_cast<Derived*>(this)->drawImpl();
}
double area() {
return static_cast<Derived*>(this)->areaImpl();
}
};
static_cast<Derived*>(this) to call derived class implementations. This cast is safe because the template guarantees Derived actually inherits from Shape<Derived>. The compiler knows the exact type at compile time, so it can inline the calls and optimize aggressively - no vtable lookup, no indirection. This gives you the flexibility of polymorphism with the performance of direct function calls. It's called "curiously recurring" because the class inherits from a template instantiated with itself.
class Circle : public Shape<Circle> {
public:
Circle(double r) : radius_(r) {}
void drawImpl() {
std::cout << "Drawing Circle with radius " << radius_ << std::endl;
}
double areaImpl() {
return 3.14159 * radius_ * radius_;
}
private:
double radius_;
};
Shape<Circle> (the curiously recurring part) and provides drawImpl() and areaImpl() implementations. When you call circle.draw(), the Shape template's draw() method casts this to Circle* and calls drawImpl(). The compiler sees the exact type (Circle) at compile time and can inline the entire call chain. This achieves code reuse (Shape provides the interface) with zero runtime cost. Compare this to virtual functions which require runtime type identification and vtable lookups.
class Square : public Shape<Square> {
public:
Square(double s) : side_(s) {}
void drawImpl() {
std::cout << "Drawing Square with side " << side_ << std::endl;
}
double areaImpl() {
return side_ * side_;
}
private:
double side_;
};
Shape<Square> and providing its own implementations. Each shape gets its own specialized version of the Shape template. The beauty of CRTP is that Circle and Square are completely independent - they don't share a vtable, and you can't store them in the same container polymorphically (they're different types). This is a trade-off: you lose runtime polymorphism but gain performance and enable compile-time optimizations. CRTP is ideal when you know all types at compile time and need maximum performance.
Pimpl - Pointer to Implementation
The Pimpl (Pointer to Implementation) idiom hides implementation details from the header file. Only a forward declaration of the implementation class appears in the header - all the actual implementation is in the source file. This reduces compilation dependencies and compile times.
When you change implementation details (private members), only the .cpp file needs recompilation - not all the files that include the header. This is especially valuable in large projects.
// Widget.h - Header file (minimal dependencies)
#ifndef WIDGET_H
#define WIDGET_H
#include <memory>
#include <string>
class Widget {
public:
Widget(const std::string& name);
~Widget(); // Must be declared for unique_ptr with incomplete type
<memory> and <string>) and forward-declares the Impl class. This is the Pimpl idiom's key benefit: hiding implementation details from the header. The destructor must be declared (and defined in the .cpp file) because std::unique_ptr needs to know how to delete Impl, which is an incomplete type in the header. Users of Widget see only the public interface - they have no idea what private members or dependencies exist in the implementation.
// Move operations
Widget(Widget&& other) noexcept;
Widget& operator=(Widget&& other) noexcept;
// Public interface
void doSomething();
std::string getName() const;
std::unique_ptr needs a complete type to move. They'll be defaulted in the .cpp file where Impl is fully defined. Copy operations are implicitly deleted because std::unique_ptr is not copyable - if you need copying, you'd have to implement deep cloning manually. The public interface declares only the methods users need - all implementation is hidden behind this minimal API.
private:
class Impl; // Forward declaration only!
std::unique_ptr<Impl> pImpl_;
};
#endif
unique_ptr to it. This is the essence of Pimpl: the pointer implementation. The actual Impl class definition is in the .cpp file, completely hidden from header users. When you change Impl's private members or include different headers in the implementation, only Widget.cpp recompiles - files that include Widget.h don't see any changes. In large projects, this dramatically reduces build times. The unique_ptr handles memory management automatically, making Pimpl exception-safe.
// Widget.cpp - Implementation file (all the details hidden here)
#include "Widget.h"
#include <iostream>
#include <vector> // Only needed in .cpp, not exposed in header
class Widget::Impl {
public:
Impl(const std::string& name) : name_(name) {}
void doSomething() {
std::cout << "Widget " << name_ << " doing something!" << std::endl;
for (int i = 0; i < 3; ++i) {
data_.push_back(i);
}
}
std::string getName() const { return name_; }
private:
std::string name_;
std::vector<int> data_; // Implementation detail hidden from header
};
<iostream> and <vector> are only included in the .cpp file - header users don't pay for these dependencies. The data_ vector is a private member that header users never see. If you change vector<int> to list<int> or add ten more private members, only Widget.cpp recompiles. This compilation firewall is invaluable in large projects where changing a widely-included header can trigger rebuilding thousands of files. Pimpl isolates implementation changes.
Widget::Widget(const std::string& name)
: pImpl_(std::make_unique<Impl>(name)) {}
Widget::~Widget() = default;
Widget::Widget(Widget&&) noexcept = default;
Widget& Widget::operator=(Widget&&) noexcept = default;
std::unique_ptr's destructor needs to delete Impl, requiring the complete type. The forwarding functions simply delegate to pImpl_ - they're trivial wrappers that add one level of indirection. This small runtime cost (pointer dereference) is usually negligible compared to the compilation time savings.
void Widget::doSomething() {
pImpl_->doSomething();
}
std::string Widget::getName() const {
return pImpl_->getName();
}
pImpl_ pointer. This forwarding is the pattern's main runtime cost - an extra pointer dereference per call. However, modern CPUs handle this efficiently, and the compiler can often inline these forwarding functions. The trade-off is clear: slightly higher runtime cost (one pointer dereference) for dramatically lower compile-time cost (isolated implementation changes). Pimpl is most valuable for widely-used classes with frequently changing implementations in large codebases.
Practice Questions: Modern C++ Idioms
Problem: Create a Timer class that prints elapsed time when it goes out of scope.
Requirements:
- Constructor stores current time
- Destructor prints "Elapsed: X ms"
- Use
std::chronofor time measurement
Show Solution
#include <iostream>
#include <chrono>
#include <thread>
class Timer {
public:
Timer() : start_(std::chrono::high_resolution_clock::now()) {}
~Timer() {
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
end - start_);
std::cout << "Elapsed: " << duration.count() << " ms" << std::endl;
}
private:
std::chrono::high_resolution_clock::time_point start_;
};
int main() {
{
Timer t;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Destructor prints elapsed time
} // Elapsed: ~100 ms
return 0;
}
Problem: Create a CRTP base class that adds comparison operators.
Requirements:
- Derived class only implements
compareTo()returning -1, 0, or 1 - Base class provides
<,>,<=,>=,==,!=
Show Solution
#include <iostream>
template<typename Derived>
class Comparable {
public:
bool operator<(const Derived& other) const {
return derived().compareTo(other) < 0;
}
bool operator>(const Derived& other) const {
return derived().compareTo(other) > 0;
}
bool operator<=(const Derived& other) const {
return derived().compareTo(other) <= 0;
}
bool operator>=(const Derived& other) const {
return derived().compareTo(other) >= 0;
}
bool operator==(const Derived& other) const {
return derived().compareTo(other) == 0;
}
bool operator!=(const Derived& other) const {
return derived().compareTo(other) != 0;
}
private:
const Derived& derived() const {
return static_cast<const Derived&>(*this);
}
};
class Person : public Comparable<Person> {
public:
Person(int age) : age_(age) {}
int compareTo(const Person& other) const {
if (age_ < other.age_) return -1;
if (age_ > other.age_) return 1;
return 0;
}
private:
int age_;
};
int main() {
Person p1(25), p2(30);
std::cout << (p1 < p2) << std::endl; // 1 (true)
std::cout << (p1 == p2) << std::endl; // 0 (false)
}
Problem: Create a Database connection class using Pimpl to hide implementation details.
Requirements:
- Header file with minimal includes
- Implementation file with actual connection logic
- Methods:
connect(),disconnect(),query() - Use
std::unique_ptrfor the Impl pointer
Show Solution
// Database.h
#ifndef DATABASE_H
#define DATABASE_H
#include <memory>
#include <string>
class Database {
public:
Database();
~Database();
Database(Database&&) noexcept;
Database& operator=(Database&&) noexcept;
bool connect(const std::string& connectionString);
void disconnect();
bool query(const std::string& sql);
bool isConnected() const;
private:
class Impl;
std::unique_ptr<Impl> pImpl_;
};
#endif
// Database.cpp
#include "Database.h"
#include <iostream>
#include <vector> // Hidden from header
class Database::Impl {
public:
bool connect(const std::string& connStr) {
std::cout << "Connecting to: " << connStr << std::endl;
connectionString_ = connStr;
connected_ = true;
return true;
}
void disconnect() {
if (connected_) {
std::cout << "Disconnecting..." << std::endl;
connected_ = false;
}
}
bool query(const std::string& sql) {
if (!connected_) return false;
std::cout << "Executing: " << sql << std::endl;
queryHistory_.push_back(sql);
return true;
}
bool isConnected() const { return connected_; }
private:
std::string connectionString_;
bool connected_ = false;
std::vector<std::string> queryHistory_; // Hidden detail
};
Database::Database() : pImpl_(std::make_unique<Impl>()) {}
Database::~Database() = default;
Database::Database(Database&&) noexcept = default;
Database& Database::operator=(Database&&) noexcept = default;
bool Database::connect(const std::string& cs) { return pImpl_->connect(cs); }
void Database::disconnect() { pImpl_->disconnect(); }
bool Database::query(const std::string& sql) { return pImpl_->query(sql); }
bool Database::isConnected() const { return pImpl_->isConnected(); }
// main.cpp
int main() {
Database db;
db.connect("mysql://localhost:3306/mydb");
db.query("SELECT * FROM users");
db.disconnect();
}
Problem: Create an RAII wrapper that automatically closes database connections.
Requirements:
- Constructor opens connection and prints status
- Destructor automatically closes connection
- Delete copy operations (non-copyable)
- Method
execute()to run queries
Show Solution
#include <iostream>
#include <string>
#include <stdexcept>
class DatabaseConnection {
public:
explicit DatabaseConnection(const std::string& connStr)
: connectionString_(connStr), connected_(false) {
// Simulate connection
std::cout << "Opening connection to: " << connStr << std::endl;
connected_ = true;
std::cout << "Connection established!" << std::endl;
}
~DatabaseConnection() {
if (connected_) {
std::cout << "Closing connection to: " << connectionString_ << std::endl;
connected_ = false;
std::cout << "Connection closed." << std::endl;
}
}
// Non-copyable
DatabaseConnection(const DatabaseConnection&) = delete;
DatabaseConnection& operator=(const DatabaseConnection&) = delete;
// Allow move
DatabaseConnection(DatabaseConnection&& other) noexcept
: connectionString_(std::move(other.connectionString_)),
connected_(other.connected_) {
other.connected_ = false;
}
void execute(const std::string& query) {
if (!connected_) {
throw std::runtime_error("Not connected!");
}
std::cout << "Executing: " << query << std::endl;
}
bool isConnected() const { return connected_; }
private:
std::string connectionString_;
bool connected_;
};
void processData() {
DatabaseConnection conn("mysql://localhost/test");
conn.execute("SELECT * FROM users");
conn.execute("UPDATE users SET active=1");
// Connection automatically closed when conn goes out of scope
}
int main() {
std::cout << "=== Starting process ===" << std::endl;
processData();
std::cout << "=== Process complete ===" << std::endl;
// Output shows connection is properly closed
}
Key Takeaways
Singleton for Global Access
Use Singleton when you need exactly one instance with global access. Use Meyers Singleton for thread safety.
Factory for Flexibility
Use Factory to decouple object creation from usage. Makes adding new types easy without changing client code.
Observer for Events
Use Observer for one-to-many notifications. Perfect for event systems, MVC, and reactive programming.
Strategy for Algorithms
Use Strategy when you need interchangeable algorithms. Switch behavior at runtime without conditionals.
RAII for Resources
Always use RAII for resource management. Let destructors handle cleanup automatically - no leaks, no forgotten unlocks.
Decorator for Extensions
Use Decorator to add behavior dynamically. Compose features at runtime without subclass explosion.
Knowledge Check
Quick Quiz
Test what you've learned about C++ design patterns