Event Bus и паттерн Observer в C++: от теории к практике
В современной разработке программного обеспечения на C++ важную роль играет слабая связность компонентов. Паттерн Observer (Наблюдатель) и его эволюция — Event Bus (Шина событий) — предоставляют элегантный механизм для организации взаимодействия между объектами без создания жёстких зависимостей. В этой статье мы подробно разберём, как реализовать эти паттерны на C++, рассмотрим их преимущества и ограничения, а также приведём практические примеры кода.
Паттерн Observer: классическая реализация
Паттерн Observer определяет отношение "один ко многим" между объектами: когда один объект (субъект) изменяет своё состояние, все зависимые от него объекты (наблюдатели) автоматически уведомляются об этом. В C++ классическая реализация выглядит следующим образом:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
// Интерфейс наблюдателя
class IObserver {
public:
virtual ~IObserver() = default;
virtual void update(const std::string& message) = 0;
};
// Конкретный наблюдатель
class ConcreteObserver : public IObserver {
private:
std::string name;
public:
explicit ConcreteObserver(const std::string& n) : name(n) {}
void update(const std::string& message) override {
std::cout << "[" << name << "] Получено: " << message << std::endl;
}
};
// Субъект (наблюдаемый объект)
class Subject {
private:
std::vector<std::shared_ptr<IObserver>> observers;
std::string state;
public:
void attach(std::shared_ptr<IObserver> observer) {
observers.push_back(observer);
}
void detach(std::shared_ptr<IObserver> observer) {
observers.erase(
std::remove(observers.begin(), observers.end(), observer),
observers.end()
);
}
void notify() {
for (auto& observer : observers) {
observer->update(state);
}
}
void setState(const std::string& newState) {
state = newState;
notify();
}
};
int main() {
Subject subject;
auto obs1 = std::make_shared<ConcreteObserver>("Наблюдатель 1");
auto obs2 = std::make_shared<ConcreteObserver>("Наблюдатель 2");
subject.attach(obs1);
subject.attach(obs2);
subject.setState("Событие #1 произошло!");
subject.detach(obs1);
subject.setState("Событие #2 без первого наблюдателя");
return 0;
}
Event Bus: централизованная шина событий
Event Bus расширяет идею Observer, предоставляя централизованный механизм обмена сообщениями. Вместо того чтобы каждый субъект хранил список наблюдателей, все подписки и публикации проходят через единую шину. Это позволяет:
- Уменьшить связанность — отправители и получатели ничего не знают друг о друге.
- Упростить масштабирование — новые типы событий добавляются без изменения существующего кода.
- Обеспечить гибкость — можно добавлять и удалять подписчиков в runtime.
Пример реализации Event Bus на C++ с использованием шаблонов и std::function:
#include <iostream>
#include <unordered_map>
#include <functional>
#include <any>
#include <typeindex>
#include <vector>
#include <memory>
// Базовый класс для событий
struct Event {
virtual ~Event() = default;
};
// Конкретное событие
struct UserLoginEvent : public Event {
std::string username;
explicit UserLoginEvent(std::string name) : username(std::move(name)) {}
};
// Класс Event Bus
class EventBus {
private:
std::unordered_map<std::type_index, std::vector<std::function<void(const std::any&)>>> handlers;
public:
template<typename EventType>
void subscribe(std::function<void(const EventType&)> handler) {
auto wrapped = [handler](const std::any& event) {
handler(std::any_cast<const EventType&>(event));
};
handlers[std::type_index(typeid(EventType))].push_back(wrapped);
}
template<typename EventType>
void publish(const EventType& event) {
auto it = handlers.find(std::type_index(typeid(EventType)));
if (it != handlers.end()) {
for (const auto& handler : it->second) {
handler(std::make_any<EventType>(event));
}
}
}
};
// Пример использования
int main() {
EventBus bus;
// Подписка на событие
bus.subscribe<UserLoginEvent>([](const UserLoginEvent& event) {
std::cout << "Пользователь вошёл в систему: " << event.username << std::endl;
});
// Публикация события
bus.publish(UserLoginEvent("Алексей"));
bus.publish(UserLoginEvent("Ма Также в библиотеке
Работа с YAML в Swift (iOS/macOS) — библиотеки Yams и YamlSwift
dashmap для Rust: полное руководство с примерами
Email отправка на TypeScript: полное руководство с nodemailer и SMTP
Авторизация и аутентификация в PHP: полное руководство с примерами