Паттерны проектирования в C++ с примерами: от А до Я
Паттерны проектирования (design patterns) — это проверенные решения типовых задач, возникающих при разработке программного обеспечения. Для C++ эти шаблоны особенно важны, так как язык сочетает объектно-ориентированное, обобщённое и низкоуровневое программирование. В этой статье мы разберём ключевые паттерны проектирования в C++ с примерами кода, которые помогут вам писать более гибкий, переиспользуемый и поддерживаемый код.
Мы рассмотрим три основные категории паттернов по классификации GoF (Gang of Four): порождающие, структурные и поведенческие. Каждый паттерн будет проиллюстрирован реальным примером на C++.
1. Порождающие паттерны (Creational Patterns)
Порождающие паттерны абстрагируют процесс создания объектов, делая систему независимой от того, как объекты создаются, компонуются и представляются.
1.1. Singleton (Одиночка)
Singleton гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В C++ часто реализуется через статический локальный объект (thread-safe с C++11).
class Logger {
public:
static Logger& getInstance() {
static Logger instance; // thread-safe инициализация
return instance;
}
void log(const std::string& message) {
std::cout << "[LOG] " << message << std::endl;
}
private:
Logger() = default;
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
};
// Использование
int main() {
Logger::getInstance().log("Приложение запущено");
return 0;
}
Когда использовать: управление конфигурацией, пулы потоков, системы логирования.
1.2. Factory Method (Фабричный метод)
Factory Method определяет интерфейс для создания объекта, но позволяет подклассам решать, какой класс инстанциировать.
class Product {
public:
virtual void use() = 0;
virtual ~Product() = default;
};
class ConcreteProductA : public Product {
public:
void use() override { std::cout << "Продукт A" << std::endl; }
};
class ConcreteProductB : public Product {
public:
void use() override { std::cout << "Продукт B" << std::endl; }
};
class Creator {
public:
virtual Product* createProduct() = 0;
void someOperation() {
Product* p = createProduct();
p->use();
delete p;
}
virtual ~Creator() = default;
};
class CreatorA : public Creator {
public:
Product* createProduct() override { return new ConcreteProductA(); }
};
class CreatorB : public Creator {
public:
Product* createProduct() override { return new ConcreteProductB(); }
};
Когда использовать: когда класс не знает, объекты каких подклассов ему нужно создавать.
2. Структурные паттерны (Structural Patterns)
Структурные паттерны описывают, как классы и объекты могут быть объединены в более крупные структуры, сохраняя при этом гибкость и эффективность.
2.1. Adapter (Адаптер)
Adapter позволяет объектам с несовместимыми интерфейсами работать вместе. В C++ часто реализуется через наследование или композицию.
// Целевой интерфейс
class Target {
public:
virtual void request() const {
std::cout << "Обычный запрос" << std::endl;
}
virtual ~Target() = default;
};
// Адаптируемый класс
class Adaptee {
public:
void specificRequest() const {
std::cout << "Специфический запрос" << std::endl;
}
};
// Адаптер (через композицию)
class Adapter : public Target {
private:
Adaptee* adaptee_;
public:
Adapter(Adaptee* a) : adaptee_(a) {}
void request() const override {
adaptee_->specificRequest();
}
};
// Использование
int main() {
Adaptee* adaptee = new Adaptee();
Target* target = new Adapter(adaptee);
target->request(); // Выведет: Специфический запрос
delete target;
delete adaptee;
return 0;
}
Когда использовать: интеграция с legacy-кодом, работа с библиотеками с несовместимыми API.
2.2. Composite (Компоновщик)
Composite позволяет сгруппировать объекты в древовидные структуры и работать с ними как с единичными объектами.
#include <vector>
class Component {
public:
virtual void operation() const = 0;
virtual void add(Component*) {}
virtual void remove(Component*) {}
virtual ~Component() = default;
};
class Leaf : public Component {
public:
void operation() const override {
std::cout << "Лист" << std::endl;
}
};
class Composite : public Component {
private:
std::vector<Component*> children_;
public:
void add(Component* c) override { children_.push_back(c); }
void operation() const override {
std::cout << "Композит (";
for (const auto& child : children_) {
child->operation();
}
std::cout << ")" << std::endl;
}
};
Когда использовать: работа с иерархиями (файловая система, GUI-виджеты).
3. Поведенческие паттерны (Behavioral Patterns)
Поведенческие паттерны определяют алгоритмы и способы взаимодействия между объектами.
3.1. Observer (Наблюдатель)
Observer определяет зависимость "один ко многим" между объектами так, что при изменении состояния одного объекта все зависимые объекты уведомляются и обновляются автоматически.
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(const std::string& message) = 0;
virtual ~Observer() = default;
};
class Subject {
private:
std::vector<Observer*> observers_;
public:
void attach(Observer* o) { observers_.push_back(o); }
void detach(Observer* o) {
observers_.erase(std::remove(observers_.begin(), observers_.end(), o), observers_.end());
}
void notify(const std::string& msg) {
for (auto* obs : observers_) {
obs->update(msg);
}
}
};
class ConcreteObserver : public Observer {
private:
std::string name_;
public:
ConcreteObserver(const std::string& name) : name_(name) {}
void update(const std::string& message) override {
std::cout << name_ << " получил: "