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

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

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

Начать курс

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

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

1. Паттерн Visitor (Посетитель) — расширение функциональности без изменения классов

Паттерн Visitor позволяет добавлять новые операции к иерархии классов, не изменяя сами классы. Это особенно полезно, когда у вас есть стабильная структура объектов, но часто меняющиеся алгоритмы их обработки.

Пример: обработка геометрических фигур

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

// Forward declaration
class Circle;
class Rectangle;

// Абстрактный посетитель
class Visitor {
public:
    virtual void visit(Circle& circle) = 0;
    virtual void visit(Rectangle& rectangle) = 0;
    virtual ~Visitor() = default;
};

// Абстрактный элемент
class Shape {
public:
    virtual void accept(Visitor& visitor) = 0;
    virtual ~Shape() = default;
};

// Конкретные элементы
class Circle : public Shape {
public:
    double radius;
    explicit Circle(double r) : radius(r) {}
    void accept(Visitor& visitor) override {
        visitor.visit(*this);
    }
};

class Rectangle : public Shape {
public:
    double width, height;
    Rectangle(double w, double h) : width(w), height(h) {}
    void accept(Visitor& visitor) override {
        visitor.visit(*this);
    }
};

// Конкретный посетитель — вычисление площади
class AreaVisitor : public Visitor {
public:
    double totalArea = 0.0;
    void visit(Circle& circle) override {
        totalArea += 3.14159 * circle.radius * circle.radius;
    }
    void visit(Rectangle& rectangle) override {
        totalArea += rectangle.width * rectangle.height;
    }
};

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(5.0));
    shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));

    AreaVisitor areaCalc;
    for (auto& shape : shapes) {
        shape->accept(areaCalc);
    }
    std::cout << "Total area: " << areaCalc.totalArea << std::endl;
    return 0;
}

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

2. Паттерн Strategy (Стратегия) — гибкая смена алгоритмов на лету

Strategy инкапсулирует семейство взаимозаменяемых алгоритмов и позволяет подменять их во время выполнения. В C++ это часто реализуется через std::function или виртуальные классы.

Пример: система сортировки данных

#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
#include <functional>

// Контекст, использующий стратегию
class Sorter {
private:
    std::function<void(std::vector<int>&)> strategy_;
public:
    explicit Sorter(std::function<void(std::vector<int>&)> strategy)
        : strategy_(std::move(strategy)) {}

    void setStrategy(std::function<void(std::vector<int>&)> newStrategy) {
        strategy_ = std::move(newStrategy);
    }

    void sort(std::vector<int>& data) {
        if (strategy_) {
            strategy_(data);
        }
    }
};

// Конкретные стратегии
void bubbleSort(std::vector<int>& data) {
    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]);
            }
        }
    }
}

void quickSort(std::vector<int>& data) {
    std::sort(data.begin(), data.end());
}

int main() {
    std::vector<int> numbers = {5, 3, 8, 1, 2};

    Sorter sorter(bubbleSort);
    sorter.sort(numbers); // Используем пузырьковую сортировку

    sorter.setStrategy(quickSort);
    sorter.sort(numbers); // Переключаемся на быструю сортировку

    for (int n : numbers) {
        std::cout << n << " ";
    }
    return 0;
}

Когда использовать: когда у вас есть несколько вариантов алгоритмов, которые могут меняться динамически. Отлично сочетается с Dependency Injection.

3. Паттерн State (Состояние) — управление поведением объекта через состояния

State позволяет объекту изменять своё поведение при изменении внутреннего состояния. В C++ это часто реализуется через конечные автоматы.

Пример: автомат для кофемашины

#include <iostream>
#include <memory>

class CoffeeMachine; // Forward declaration

// Абстрактное состояние
class State {
public:
    virtual void insertCoin(CoffeeMachine& machine) = 0;
    virtual void selectDrink(CoffeeMachine& machine) = 0;
    virtual void dispense(CoffeeMachine& machine) = 0;
    virtual ~State() = default;
};

// Конкретные состояния
class IdleState : public State {
public:
    void insertCoin(CoffeeMachine& machine) override;
    void selectDrink(CoffeeMachine& machine) override;
    void dispense(CoffeeMachine& machine) override;
};

class HasCoinState : public State {
public:
    void insertCoin(CoffeeMachine& machine) override;
    void selectDrink(CoffeeMachine& machine) override;
    void dispense(CoffeeMachine& machine) override;
};

class DispensingState : public State {
public:
    void insertCoin(CoffeeMachine& machine) override;
    void selectDrink(CoffeeMachine& machine) override;
    void dispense(CoffeeMachine& machine) override;
};

// Контекст — кофемашина
class CoffeeMachine {
private:
    std::unique_ptr<State> state_;
public:
    CoffeeMachine() : state_(std::make_unique<IdleState>()) {}
    void setState(std::unique_ptr<State> newState) {
        state_ = std::move(newState);
    }
    void insertCoin() { state_->insertCoin(*this); }
    void selectDrink() { state_->selectDrink(*this); }
    void dispense() { state_-&                

Блоги

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