C++ design patterns provide proven solutions to common programming challenges, enhancing code quality and maintainability. These established approaches help developers create more flexible, reusable, and efficient software systems. This article explores several fundamental design patterns in C++ implementation, including creation, structural, and behavioral patterns that address various architectural concerns.
The primary advantages of implementing design patterns include:
- Enhanced code readability and maintainability
- Isolation of common design issues from business logic
- Facilitation of code reuse
- Elimination of redundant code
- Simplification of complex implementation challenges
Factory Pattern The factory pattern in C++ offers an effective mechanism for object creation, allowing a specialized class to instantiate various concrete classes that implement a common interface without explicitly specifying their concrete types. By abstracting the instantiation process through a factory class, the pattern significantly improves code maintainability and simplifies the addition of new object types.
#include <iostream>
#include <memory>
// Product interface
interface class IProduct {
public:
virtual void display() = 0;
virtual ~IProduct() = default;
};
// Concrete product implementation
class StandardProduct : public IProduct {
public:
void display() override {
std::cout << "Standard product instance created" << std::endl;
}
};
// Factory interface
interface class IFactory {
public:
virtual std::unique_ptr<IProduct> manufacture() = 0;
virtual ~IFactory() = default;
};
// Concrete factory implementation
class StandardFactory : public IFactory {
public:
std::unique_ptr<IProduct> manufacture() override {
return std::make_unique<StandardProduct>();
}
};
// Client code
int main() {
std::unique_ptr<IFactory> factory = std::make_unique<StandardFactory>();
std::unique_ptr<IProduct> product = factory->manufacture();
product->display();
return 0;
}
Singleton Pattern The singleton pattern ensures a class has only one instance and provides a global point of access to it. This pattern is particularly useful when exactly one object is needed to coordinate actions across a system.
class UniqueInstance {
private:
static UniqueInstance* singleInstance;
UniqueInstance() {} // Private constructor
// Delete copy constructor and assignment operator
UniqueInstance(const UniqueInstance&) = delete;
UniqueInstance& operator=(const UniqueInstance&) = delete;
public:
static UniqueInstance* getInstance() {
if (singleInstance == nullptr) {
singleInstance = new UniqueInstance();
}
return singleInstance;
}
void performOperation() {
std::cout << "Singleton operation executed" << std::endl;
}
};
// Initialize static member
UniqueInstance* UniqueInstance::singleInstance = nullptr;
For thread-safe implementation in C++11 and later:
class ThreadSafeSingleton {
private:
ThreadSafeSingleton() {} // Private constructor
// Delete copy constructor and assignment operator
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
public:
static ThreadSafeSingleton& getInstance() {
static ThreadSafeSingleton instance;
return instance;
}
void executeTask() {
std::cout << "Thread-safe singleton task completed" << std::endl;
}
};
Adapter Pattern The adapter pattern allows incompatible interfaces to work together by wrapping an existing class with a new interface. This structural pattern enables classes with different interfaces to collaborate without modifying their source code.
#include <iostream>
// Target interface
interface class ITarget {
virtual void request() = 0;
virtual ~ITarget() = default;
};
// Adaptee class with incompatible interface
class LegacyService {
public:
void specificProcessing() {
std::cout << "Legacy service processing" << std::endl;
}
};
// Adapter class
class ServiceAdapter : public ITarget {
private:
LegacyService* legacyService;
public:
ServiceAdapter(LegacyService* service) : legacyService(service) {}
void request() override {
legacyService->specificProcessing();
}
};
// Client code
int main() {
LegacyService legacyService;
ITarget* adapter = new ServiceAdapter(&legacyService);
adapter->request();
delete adapter;
return 0;
}
Facade Pattern The facade pattern provides a simplified interface to a complex subsystem, hiding its complexity behind a single unified interface. This structural pattern is particularly useful when dealing with systems that consist of multiple interdependent classes.
#include <iostream>
// Subsystem components
class Processor {
public:
void initialize() {
std::cout << "Processor initialized" << std::endl;
}
void execute() {
std::cout << "Processing data" << std::endl;
}
};
class Memory {
public:
void allocate(int size) {
std::cout << "Memory allocated: " << size << " units" << std::endl;
}
};
class Storage {
public:
void loadData() {
std::cout << "Data loaded from storage" << std::endl;
}
};
// Facade class
class SystemFacade {
private:
Processor processor;
Memory memory;
Storage storage;
public:
void startSystem() {
memory.allocate(1024);
processor.initialize();
storage.loadData();
processor.execute();
}
};
// Client code
int main() {
SystemFacade system;
system.startSystem();
return 0;
}
Proxy Pattern The proxy pattern provides a surrogate or placeholder for another object to control access to it. This structural pattern allows for sophisticated control over when and how an object is accessed, enabling functionality such as lazy initialization, access control, or logging.
#include <iostream>
// Subject interface
interface class ISubject {
virtual void performAction() = 0;
virtual ~ISubject() = default;
};
// Real subject
class RealSubject : public ISubject {
public:
void performAction() override {
std::cout << "Real subject performing action" << std::endl;
}
};
// Proxy class
class ProxySubject : public ISubject {
private:
RealSubject* realSubject;
bool initialized;
public:
ProxySubject() : realSubject(nullptr), initialized(false) {}
~ProxySubject() {
delete realSubject;
}
void performAction() override {
if (!initialized) {
realSubject = new RealSubject();
initialized = true;
std::cout << "Proxy initialized real subject" << std::endl;
}
realSubject->performAction();
}
};
// Client code
int main() {
ISubject* proxy = new ProxySubject();
proxy->performAction(); // Initialization happens here
proxy->performAction(); // Uses existing instance
delete proxy;
return 0;
}
Bridge Pattern The bridge pattern decouples an abstraction from its implementation, allowing both to vary independently. This structural pattern is particularly useful when you need to support multiple implementations of an abstraction and avoid a peramnent binding between the abstraction and its implementation.
#include <iostream>
#include <memory>
// Implementor interface
interface class IImplementor {
virtual void operationImpl() = 0;
virtual ~IImplementor() = default;
};
// Concrete implementor A
class ImplementorA : public IImplementor {
public:
void operationImpl() override {
std::cout << "Implementation A operation" << std::endl;
}
};
// Concrete implementor B
class ImplementorB : public IImplementor {
public:
void operationImpl() override {
std::cout << "Implementation B operation" << std::endl;
}
};
// Abstraction
class Abstraction {
protected:
IImplementor* implementor;
public:
Abstraction(IImplementor* impl) : implementor(impl) {}
virtual void operation() = 0;
virtual ~Abstraction() = default;
};
// Refined abstraction
class RefinedAbstraction : public Abstraction {
public:
RefinedAbstraction(IImplementor* impl) : Abstraction(impl) {}
void operation() override {
std::cout << "Refined abstraction operation with ";
implementor->operationImpl();
}
};
// Client code
int main() {
std::unique_ptr<IImplementor> implA = std::make_unique<ImplementorA>();
std::unique_ptr<IImplementor> implB = std::make_unique<ImplementorB>();
std::unique_ptr<Abstraction> abstractionA = std::make_unique<RefinedAbstraction>(implA.get());
std::unique_ptr<Abstraction> abstractionB = std::make_unique<RefinedAbstraction>(implB.get());
abstractionA->operation();
abstractionB->operation();
return 0;
}
Template Method Pattern The template method pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. This behavioral pattern lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
#include <iostream>
// Abstract base class with template method
class AbstractProcess {
public:
// Template method defining the algorithm
void execute() {
this->initialize();
this->performMainTask();
this->finalize();
}
private:
virtual void initialize() = 0;
virtual void performMainTask() = 0;
virtual void finalize() = 0;
};
// Concrete implementation
class ConcreteProcess : public AbstractProcess {
private:
void initialize() override {
std::cout << "Process initialization" << std::endl;
}
void performMainTask() override {
std::cout << "Main task execution" << std::endl;
}
void finalize() override {
std::cout << "Process finalization" << std::endl;
}
};
// Client code
int main() {
AbstractProcess* process = new ConcreteProcess();
process->execute();
delete process;
return 0;
}
Strategy Pattern The strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This behavioral pattern lets the algorithm vary independently from clients that use it.
#include <iostream>
#include <memory>
// Strategy interface
interface class IStrategy {
virtual void execute() = 0;
virtual ~IStrategy() = default;
};
// Concrete strategy A
class StrategyA : public IStrategy {
public:
void execute() override {
std::cout << "Executing strategy A" << std::endl;
}
};
// Concrete strategy B
class StrategyB : public IStrategy {
public:
void execute() override {
std::cout << "Executing strategy B" << std::endl;
}
};
// Context class
class Context {
private:
std::unique_ptr<IStrategy> strategy;
public:
void setStrategy(std::unique_ptr<IStrategy> newStrategy) {
strategy = std::move(newStrategy);
}
void performOperation() {
if (strategy) {
strategy->execute();
} else {
std::cout << "No strategy set" << std::endl;
}
}
};
// Client code
int main() {
Context context;
context.setStrategy(std::make_unique<StrategyA>());
context.performOperation();
context.setStrategy(std::make_unique<StrategyB>());
context.performOperation();
return 0;
}
Observer Pattern The observer pattern defines a one-to-many dependency between objects sothat when one object changes state, all its dependents are notified and updated automatically. This behavioral pattern is essential for implementing distributed event handling systems.
#include <iostream>
#include <vector>
#include <memory>
// Observer interface
interface class IObserver {
virtual void update(const std::string& message) = 0;
virtual ~IObserver() = default;
};
// Subject interface
interface class ISubject {
virtual void attach(std::shared_ptr<IObserver> observer) = 0;
virtual void detach(std::shared_ptr<IObserver> observer) = 0;
virtual void notify() = 0;
virtual ~ISubject() = default;
};
// Concrete subject
class ConcreteSubject : public ISubject {
private:
std::vector<std::shared_ptr<IObserver>> observers;
std::string state;
public:
void attach(std::shared_ptr<IObserver> observer) override {
observers.push_back(observer);
}
void detach(std::shared_ptr<IObserver> observer) override {
// Remove observer from vector
observers.erase(
std::remove(observers.begin(), observers.end(), observer),
observers.end()
);
}
void notify() override {
for (auto& observer : observers) {
observer->update(state);
}
}
void setState(const std::string& newState) {
state = newState;
notify();
}
};
// Concrete observer
class ConcreteObserver : public IObserver {
private:
std::string name;
public:
ConcreteObserver(const std::string& observerName) : name(observerName) {}
void update(const std::string& message) override {
std::cout << name << " received update: " << message << std::endl;
}
};
// Client code
int main() {
auto subject = std::make_shared<ConcreteSubject>();
auto observer1 = std::make_shared<ConcreteObserver>("Observer 1");
auto observer2 = std::make_shared<ConcreteObserver>("Observer 2");
subject->attach(observer1);
subject->attach(observer2);
subject->setState("State has changed");
subject->detach(observer1);
subject->setState("Second state change");
return 0;
}
Chain of Responsibility Pattern The chain of responsibility pattern creates a chain of processing objects for a request. Each processing object contains logic that defines the types of requests it can handle and how to pass the request to the next processor in the chain.
#include <iostream>
#include <memory>
// Handler interface
interface class IHandler {
public:
virtual void setNext(std::shared_ptr<IHandler> handler) = 0;
virtual void handle(const std::string& request) = 0;
virtual ~IHandler() = default;
};
// Base handler implementation
class BaseHandler : public IHandler {
protected:
std::shared_ptr<IHandler> nextHandler;
public:
void setNext(std::shared_ptr<IHandler> handler) override {
nextHandler = handler;
}
void handle(const std::string& request) override {
if (nextHandler) {
nextHandler->handle(request);
}
}
};
// Concrete handler A
class HandlerA : public BaseHandler {
public:
void handle(const std::string& request) override {
if (request == "A") {
std::cout << "HandlerA processed request: " << request << std::endl;
} else {
BaseHandler::handle(request);
}
}
};
// Concrete handler B
class HandlerB : public BaseHandler {
public:
void handle(const std::string& request) override {
if (request == "B") {
std::cout << "HandlerB processed request: " << request << std::endl;
} else {
BaseHandler::handle(request);
}
}
};
// Concrete handler C
class HandlerC : public BaseHandler {
public:
void handle(const std::string& request) override {
if (request == "C") {
std::cout << "HandlerC processed request: " << request << std::endl;
} else {
std::cout << "No handler could process request: " << request << std::endl;
}
}
};
// Client code
int main() {
auto handlerA = std::make_shared<HandlerA>();
auto handlerB = std::make_shared<HandlerB>();
auto handlerC = std::make_shared<HandlerC>();
// Set up the chain
handlerA->setNext(handlerB);
handlerB->setNext(handlerC);
// Process requests
handlerA->handle("A");
handlerA->handle("B");
handlerA->handle("C");
handlerA->handle("D"); // Unsupported request
return 0;
}