Введение в паттерны проектирования на C++
Паттерны проектирования (design patterns) — это проверенные временем архитектурные решения для типовых задач разработки. В мире C++ они особенно важны, так как позволяют создавать гибкий, расширяемый и поддерживаемый код. В этой статье мы разберём четыре фундаментальных паттерна: Singleton, Factory Method, Observer и Strategy — с полными примерами на современном C++ (C++17/20).
Каждый паттерн будет сопровождаться практическим примером, диаграммой классов и пояснениями по применению. Материал рассчитан на разработчиков, уже знакомых с основами C++ и ООП.
1. Порождающий паттерн Singleton (Одиночка)
Singleton гарантирует, что класс имеет только один экземпляр, и предоставляет глобальную точку доступа к нему. В C++ классическая реализация использует статический метод и приватный конструктор.
Когда применять
- Управление соединением с базой данных
- Система логирования
- Кэш-менеджер
- Конфигурация приложения
Пример реализации (потокобезопасный Singleton)
#include <iostream>
#include <mutex>
#include <memory>
class Logger {
private:
static std::unique_ptr<Logger> instance;
static std::mutex mtx;
Logger() {
std::cout << "Logger создан" << std::endl;
}
public:
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
static Logger* getInstance() {
std::lock_guard<std::mutex> lock(mtx);
if (!instance) {
instance.reset(new Logger());
}
return instance.get();
}
void log(const std::string& msg) {
std::cout << "[LOG] " << msg << std::endl;
}
};
std::unique_ptr<Logger> Logger::instance = nullptr;
std::mutex Logger::mtx;
int main() {
Logger* log1 = Logger::getInstance();
Logger* log2 = Logger::getInstance();
log1->log("Тестовое сообщение");
// log1 и log2 указывают на один и тот же объект
std::cout << "log1 == log2: " << (log1 == log2) << std::endl;
return 0;
}
Современный C++ подход (Meyers' Singleton)
class ConfigManager {
public:
static ConfigManager& getInstance() {
static ConfigManager instance; // потокобезопасно в C++11+
return instance;
}
void setParam(const std::string& key, const std::string& value) {
params[key] = value;
}
std::string getParam(const std::string& key) {
return params[key];
}
private:
ConfigManager() = default;
std::map<std::string, std::string> params;
};
2. Порождающий паттерн Factory Method (Фабричный метод)
Factory Method определяет интерфейс для создания объекта, но позволяет подклассам выбирать конкретный класс создаваемого объекта. Этот паттерн особенно полезен, когда тип объекта заранее неизвестен.
Структура паттерна
- Product — абстрактный класс продукта
- ConcreteProduct — конкретная реализация продукта
- Creator — абстрактный класс создателя с фабричным методом
- ConcreteCreator — переопределяет фабричный метод
Пример: система уведомлений
#include <iostream>
#include <memory>
#include <string>
// Абстрактный продукт
class Notification {
public:
virtual void send(const std::string& message) = 0;
virtual ~Notification() = default;
};
// Конкретные продукты
class EmailNotification : public Notification {
public:
void send(const std::string& message) override {
std::cout << "Email: " << message << std::endl;
}
};
class SMSNotification : public Notification {
public:
void send(const std::string& message) override {
std::cout << "SMS: " << message << std::endl;
}
};
class PushNotification : public Notification {
public:
void send(const std::string& message) override {
std::cout << "Push: " << message << std::endl;
}
};
// Абстрактный создатель
class NotificationFactory {
public:
virtual std::unique_ptr<Notification> createNotification() = 0;
virtual ~NotificationFactory() = default;
void notify(const std::string& message) {
auto notification = createNotification();
notification->send(message);
}
};
// Конкретные создатели
class EmailFactory : public NotificationFactory {
public:
std::unique_ptr<Notification> createNotification() override {
return std::make_unique<EmailNotification>();
}
};
class SMSFactory : public NotificationFactory {
public:
std::unique_ptr<Notification> createNotification() override {
return std::make_unique<SMSNotification>();
}
};
// Использование
int main() {
std::unique_ptr<NotificationFactory> factory;
// Выбор фабрики в рантайме
std::string type = "email";
if (type == "email") {
factory = std::make_unique<EmailFactory>();
} else if (type == "sms") {
factory = std::make_unique<SMSFactory>();
}
factory->notify("Важное сообщение!");
return 0;
}
3. Поведенческий паттерн Observer (Наблюдатель)
Observer определяет зависимость "один ко многим" между объектами: когда один объект (субъект) меняет состояние, все зависимые объекты (наблюдатели) автоматически уведомляются и обновляются.
Пример: система мониторинга температуры
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
// Интерфейс наблюдателя
class Observer {
public:
virtual void update(float temperature) = 0;
virtual ~Observer() = default;
};
// Интерфейс субъекта
class Subject {
public:
virtual void attach(std::shared_ptr<Observer> observer) = 0;
virtual void detach(std::shared_ptr<Observer> observer) = 0;
virtual void notify() = 0;
virtual ~Subject() = default;
};
// Конкретный субъект
class WeatherStation : public Subject {
private:
std::vector<std::shared_ptr<Observer>> observers;
float temperature;