Event Bus и паттерн Observer в C++: от теории к современной реализации
В современной разработке на C++ всё чаще возникает потребность в гибком механизме обмена сообщениями между компонентами, которые не должны знать друг о друге напрямую. Паттерн Observer и его эволюция — Event Bus — решают эту задачу, позволяя строить слабо связанные, расширяемые системы. В этой статье мы разберём, как реализовать Event Bus на C++ с использованием стандартной библиотеки (C++17/20), и рассмотрим практические примеры.
Паттерн 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 Subject {
private:
std::vector<std::weak_ptr<IObserver>> observers_;
public:
void attach(std::shared_ptr<IObserver> observer) {
observers_.push_back(observer);
}
void detach(std::shared_ptr<IObserver> observer) {
observers_.erase(
std::remove_if(observers_.begin(), observers_.end(),
[&observer](const std::weak_ptr<IObserver>& wp) {
auto sp = wp.lock();
return !sp || sp == observer;
}),
observers_.end()
);
}
void notify(const std::string& message) {
for (auto it = observers_.begin(); it != observers_.end(); ) {
if (auto sp = it->lock()) {
sp->update(message);
++it;
} else {
it = observers_.erase(it); // удаляем "мёртвые" ссылки
}
}
}
};
// Конкретный наблюдатель
class ConcreteObserver : public IObserver {
private:
std::string name_;
public:
ConcreteObserver(const std::string& name) : name_(name) {}
void update(const std::string& message) override {
std::cout << "[" << name_ << "] получил: " << message << std::endl;
}
};
int main() {
auto subject = std::make_shared<Subject>();
auto obs1 = std::make_shared<ConcreteObserver>("Наблюдатель 1");
auto obs2 = std::make_shared<ConcreteObserver>("Наблюдатель 2");
subject->attach(obs1);
subject->attach(obs2);
subject->notify("Событие произошло!");
subject->detach(obs1);
subject->notify("Только для второго");
return 0;
}
Этот код работает, но у него есть ограничения: все наблюдатели должны наследовать IObserver, а сообщения — быть строками. На практике нужна более гибкая система.
Event Bus: современная эволюция
Event Bus — это централизованная шина, через которую компоненты могут публиковать события и подписываться на них. Ключевые отличия от классического Observer:
- Типизированные события — каждое событие имеет свой тип данных.
- Отсутствие жёсткой иерархии — подписчики не обязаны наследовать общий интерфейс.
- Глобальная или локальная шина — можно иметь один Event Bus на всё приложение или несколько изолированных.
Реализация Event Bus на C++17/20 с std::any
Используем std::any для хранения данных любого типа и std::function для обработчиков. Для разделения событий по типам применим std::type_index.
#include <iostream>
#include <any>
#include <functional>
#include <map>
#include <typeindex>
#include <vector>
#include <memory>
// Базовый класс события (опционально)
struct Event {
virtual ~Event() = default;
};
// Конкретные события
struct KeyEvent : public Event {
int keyCode;
bool pressed;
KeyEvent(int code, bool pr) : keyCode(code), pressed(pr) {}
};
struct MouseEvent : public Event {
int x, y;
MouseEvent(int px, int py) : x(px), y(py) {}
};
// Event Bus
class EventBus {
private:
// Карта: тип события -> список обработчиков
std::map<std::type_index, std::vector<std::function<void(const std::any&)>>> handlers_;
public:
// Подписка на событие определённого типа
template<typename EventType>
void subscribe(std::function<void(const EventType& Также в библиотеке
Генерация PDF на C++: обзор библиотек и примеры кода
Работа с CSV в JavaScript (Node.js): библиотеки и инструменты
Swagger/OpenAPI генерация для Swift: инструменты и примеры кода
Chalk для JavaScript (Node.js): полное руководство с примерами
Очереди задач в Java: ExecutorService и ThreadPoolExecutor