Паттерны проектирования в C++ с примерами: полный разбор

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

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

Начать курс

Введение: зачем нужны паттерны проектирования в C++

Паттерны проектирования (design patterns) — это проверенные решения типовых задач, возникающих при разработке программного обеспечения. В C++, с его мощной системой типов, RAII и множественным наследованием, паттерны приобретают особую гибкость. Они помогают сделать код более читаемым, поддерживаемым и масштабируемым.

В этой статье мы рассмотрим 4 ключевых паттерна из каталога GoF (Gang of Four): Singleton, Factory Method, Observer и Strategy. Каждый паттерн будет проиллюстрирован реальным примером кода на C++17/20.

Порождающие паттерны: Singleton (Одиночка)

Singleton гарантирует, что у класса существует только один экземпляр, и предоставляет глобальную точку доступа к нему. В C++ современный подход использует локальную статическую переменную (thread-safe с C++11).

Пример: Логгер приложения

class Logger {
public:
    // Удаляем копирование и присваивание
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

    static Logger& getInstance() {
        static Logger instance;  // thread-safe с C++11
        return instance;
    }

    void log(const std::string& message) {
        std::lock_guard<std::mutex> lock(m_mutex);
        std::cout << "[LOG] " << message << std::endl;
    }

private:
    Logger() = default;  // приватный конструктор
    ~Logger() = default;
    std::mutex m_mutex;
};

// Использование
int main() {
    Logger::getInstance().log("Приложение запущено");
    Logger::getInstance().log("Ошибка: файл не найден");
    return 0;
}

Плюсы: гарантия единственности, ленивая инициализация, потокобезопасность.
Минусы: усложняет тестирование (глобальное состояние), нарушает принцип единственной ответственности.

Порождающие паттерны: Factory Method (Фабричный метод)

Factory Method определяет интерфейс для создания объекта, но позволяет подклассам решать, какой класс инстанциировать. В C++ часто реализуется через виртуальные функции и умные указатели.

Пример: Генерация разных типов документов

// Абстрактный продукт
class Document {
public:
    virtual void open() = 0;
    virtual void save() = 0;
    virtual ~Document() = default;
};

// Конкретные продукты
class PDFDocument : public Document {
public:
    void open() override { std::cout << "Открыт PDF\n"; }
    void save() override { std::cout << "Сохранен PDF\n"; }
};

class WordDocument : public Document {
public:
    void open() override { std::cout << "Открыт Word\n"; }
    void save() override { std::cout << "Сохранен Word\n"; }
};

// Создатель (Creator)
class Application {
public:
    virtual std::unique_ptr<Document> createDocument() = 0;
    virtual ~Application() = default;

    void newDocument() {
        auto doc = createDocument();
        doc->open();
        // работа с документом
    }
};

// Конкретные создатели
class PDFApplication : public Application {
public:
    std::unique_ptr<Document> createDocument() override {
        return std::make_unique<PDFDocument>();
    }
};

class WordApplication : public Application {
public:
    std::unique_ptr<Document> createDocument() override {
        return std::make_unique<WordDocument>();
    }
};

// Использование
int main() {
    std::unique_ptr<Application> app = std::make_unique<PDFApplication>();
    app->newDocument();  // Вывод: "Открыт PDF"
    return 0;
}

Этот паттерн идеален, когда заранее неизвестны точные типы создаваемых объектов.

Поведенческие паттерны: Observer (Наблюдатель)

Observer определяет зависимость "один ко многим" между объектами так, что при изменении состояния одного объекта все зависящие от него уведомляются и обновляются автоматически.

Пример: Система уведомлений о погоде

#include <vector>
#include <memory>
#include <algorithm>

// Интерфейс наблюдателя
class IObserver {
public:
    virtual void update(float temperature, float humidity) = 0;
    virtual ~IObserver() = default;
};

// Наблюдаемый субъект
class WeatherStation {
private:
    std::vector<std::weak_ptr<IObserver>> observers;
    float temperature;
    float humidity;

public:
    void addObserver(std::shared_ptr<IObserver> observer) {
        observers.push_back(observer);
    }

    void removeObserver(std::shared_ptr<IObserver> observer) {
        auto it = std::remove_if(observers.begin(), observers.end(),
            [&observer](const std::weak_ptr<IObserver>& wp) {
                return wp.lock() == observer;
            });
        observers.erase(it, observers.end());
    }

    void setMeasurements(float temp, float hum) {
        temperature = temp;
        humidity = hum;
        notifyObservers();
    }

private:
    void notifyObservers() {
        for (auto& weakObs : observers) {
            if (auto obs = weakObs.lock()) {
                obs->update(temperature, humidity);
            }
        }
    }
};

// Конкретный наблюдатель
class Display : public IObserver, public std::enable_shared_from_this<Display> {
public:
    void update(float temperature, float humidity) override {
        std::cout << "Температура: " << temperature 
                  << "°C, Влажность: " << humidity << "%\n";
    }
};

// Использование
int main() {
    WeatherStation station;
    auto display = std::make_shared<Display>();
    station.addObserver(display);
    station.setMeasurements(25.5, 60.0);  // Вывод: Температура: 25.5°C, Влажность: 60%
    return 0;
}

Важно: используйте std::weak_ptr для хранения наблюдателей, чтобы избежать утечек памяти и циклических зависимостей.

Поведенческие паттерны: Strategy (Стратегия)

Strategy определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Позволяет изменять алгоритм независимо от клиентов, которые его используют.

Пример: Разные способы сортировки

#include <functional>
#include <vector>
#include <algorithm>

// Интерфейс стратегии
class SortStrategy {                

Блоги

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