Advanced Design Patterns in C++: From Theory to Real Code
Design patterns are proven solutions to typical problems in object-oriented programming. If you've already mastered basic patterns (Singleton, Factory, Observer), it's time to move to the next level. In this article, we'll cover four advanced patterns: Visitor, Strategy, State, and Command. All examples are written in modern C++17/20 using smart pointers, polymorphism, and templates.
1. Visitor Pattern
The Visitor pattern allows adding new operations to a class hierarchy without modifying the classes themselves. This is an ideal solution for working with AST trees, graphic scenes, or export systems.
Problem
You have a hierarchy of geometric shapes (Circle, Rectangle, Triangle). You need to implement operations: area calculation, rendering, JSON serialization. If you add methods to each class, the open/closed principle is violated.
Solution in C++17
#include <iostream>#include <memory>#include <vector>
// Forward declarationclass Circle;class Rectangle;
class Visitor {public: virtual void visit(Circle& c) = 0; virtual void visit(Rectangle& r) = 0; virtual ~Visitor() = default;};
class Shape {public: virtual void accept(Visitor& v) = 0; virtual ~Shape() = default;};
class Circle : public Shape {public: double radius = 5.0; void accept(Visitor& v) override { v.visit(*this); }};
class Rectangle : public Shape {public: double width = 4.0, height = 6.0; void accept(Visitor& v) override { v.visit(*this); }};
// Concrete visitorclass AreaVisitor : public Visitor {public: void visit(Circle& c) override { std::cout << "Circle area: " << 3.14159 * c.radius * c.radius << ""; } void visit(Rectangle& r) override { std::cout << "Rectangle area: " << r.width * r.height << ""; }};
int main() { std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>()); shapes.push_back(std::make_unique<Rectangle>());
AreaVisitor av; for (auto& s : shapes) { s->accept(av); } return 0;}2. Strategy Pattern
Strategy allows changing the data processing algorithm at runtime. Unlike Visitor, which works with an object hierarchy, Strategy encapsulates a single algorithm.
Real-world scenario
A file compression system. The user selects an algorithm: ZIP, GZIP, or custom. Instead of an if-else chain, we use Strategy.
Implementation with std::function
#include <iostream>#include <functional>#include <string>
class CompressionContext {private: std::function<void(const std::string&)> strategy_;public: explicit CompressionContext(std::function<void(const std::string&)> strategy) : strategy_(std::move(strategy)) {}
void setStrategy(std::function<void(const std::string&)> strategy) { strategy_ = std::move(strategy); }
void compress(const std::string& data) { if (strategy_) { strategy_(data); } else { std::cout << "No strategy set"; } }};
int main() { auto zip = [](const std::string& d) { std::cout << "Compressing with ZIP: " << d << ""; }; auto gzip = [](const std::string& d) { std::cout << "Compressing with GZIP: " << d << ""; };
CompressionContext ctx(zip); ctx.compress("Hello World"); // ZIP
ctx.setStrategy(gzip)