Паттерны проектирования в C++: продвинутые техники и примеры кода

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Продвинутые паттерны проектирования в C++: от теории к реальному коду

Паттерны проектирования — это проверенные решения типовых задач в объектно-ориентированном программировании. Если вы уже освоили базовые паттерны (Singleton, Factory, Observer), пора перейти на следующий уровень. В этой статье мы разберем четыре продвинутых паттерна: Visitor, Strategy, State и Command. Все примеры написаны на современном C++17/20 с использованием умных указателей, полиморфизма и шаблонов.

1. Паттерн Visitor (Посетитель)

Паттерн Visitor позволяет добавлять новые операции к иерархии классов без изменения самих классов. Это идеальное решение для работы с AST-деревьями, графическими сценами или системами экспорта.

Проблема

У вас есть иерархия геометрических фигур (Circle, Rectangle, Triangle). Нужно реализовать операции: вычисление площади, отрисовка, сериализация в JSON. Если добавлять методы в каждый класс, нарушается принцип открытости/закрытости.

Решение на C++17

#include <iostream>
#include <memory>
#include <vector>

// Forward declaration
class 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); }
};

// Конкретный посетитель
class AreaVisitor : public Visitor {
public:
    void visit(Circle& c) override {
        std::cout << "Circle area: " << 3.14159 * c.radius * c.radius << "\n";
    }
    void visit(Rectangle& r) override {
        std::cout << "Rectangle area: " << r.width * r.height << "\n";
    }
};

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 (Стратегия)

Strategy позволяет менять алгоритм обработки данных во время выполнения. В отличие от Visitor, который работает с иерархией объектов, Strategy инкапсулирует отдельный алгоритм.

Реальный сценарий

Система сжатия файлов. Пользователь выбирает алгоритм: ZIP, GZIP или собственный. Вместо цепочки if-else используем Strategy.

Реализация с 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\n";
        }
    }
};

int main() {
    auto zip = [](const std::string& d) {
        std::cout << "Compressing with ZIP: " << d << "\n";
    };
    auto gzip = [](const std::string& d) {
        std::cout << "Compressing with GZIP: " << d << "\n";
    };

    CompressionContext ctx(zip);
    ctx.compress("Hello World");  // ZIP

    ctx.setStrategy(gzip);
    ctx.compress("Hello World");  // GZIP
    return 0;
}

3. Паттерн State (Состояние)

State позволяет объекту менять поведение при изменении внутреннего состояния. Классический пример — конечный автомат для парсинга, сетевого протокола или игрового персонажа.

Пример: TCP-соединение

#include <iostream>
#include <memory>

class TCPConnection; // forward

class TCPState {
public:
    virtual void open(TCPConnection& conn) = 0;
    virtual void close(TCPConnection& conn) = 0;
    virtual void send(TCPConnection& conn, const std::string& data) = 0;
    virtual ~TCPState() = default;
};

class TCPConnection {
private:
    std::unique_ptr<TCPState> state_;
public:
    explicit TCPConnection(std::unique_ptr<TCPState> state) 
        : state_(std::move(state)) {}

    void setState(std::unique_ptr<TCPState> state) {
        state_ = std::move(state);
    }

    void open() { state_->open(*this); }
    void close() { state_->close(*this); }
    void send(const std::string& data) { state_->send(*this, data); }
};

// Конкретные состояния
class ClosedState : public TCPState {
public:
    void open(TCPConnection& conn) override;
    void close(TCPConnection& conn) override {
        std::cout << "Already closed\n";
    }
    void send(TCPConnection&, const std::string&) override {
        std::cout << "Cannot send, connection closed\n";
    }
};

class OpenState : public TCPState {
public:
    void open(TCPConnection&) override {
        std::cout << "Already open\n";
    }
    void close(TCPConnection& conn) override;
    void send(TCPConnection&, const std::string& data) override {
        std::cout << "Sending: " << data << "\n";
    }
};

// Реализация переходов (должна быть после определения классов)
void ClosedState::open(TCPConnection& conn) {
    std::cout << "Opening connection\n";
    conn.setState(std::make_unique<OpenState>());
}

void OpenState::close(TCPConnection& conn) {
    std::cout << "Closing connection\n";
    conn.setState(std::make_unique<ClosedState>());
}

int main() {
    TCPConnection conn(std::make_unique<ClosedState>());
    conn.send("test");   // Cannot send
    conn.open();         // Opening                

Блоги

Рекомендаций по книгам