Как я могу уменьшить связь в моей реализации Event Bus

В моем приложении у меня есть несколько модулей, которые не соответствуют отношениям «есть» или «есть», но все же должны обмениваться данными и передавать друг другу данные. Чтобы попытаться объединить эти модули, я реализовал класс Event Bus, который обрабатывает передачу сообщений от «афиш событий» к «слушателям событий».

Классы могут реализовать IEventListener если они хотят зарегистрироваться, чтобы получать определенные события. Кроме того, классы могут звонить EventBus::postEvent() если им нужно отправить событие на автобус. когда EventBus::update() называется EventBus, обрабатывает очередь запланированных сообщений и направляет их зарегистрированным слушателям.

EventBus.h

#pragma once

#include <queue>
#include <map>
#include <set>
#include <memory>class IEvent
{
public:
static enum EventType
{
EV_ENEMY_DIED,
EV_ENEMY_SPAWNED,
EV_GAME_OVER
};

virtual ~IEvent() {};
virtual EventType getType() const = 0;
};class IEventListener
{
public:
virtual void handleEvent(IEvent * const e) = 0;
};class EventBus
{
public:
EventBus() {};
~EventBus() {};

void update();
void postEvent(std::unique_ptr<IEvent> &e);
void registerListener(IEvent::EventType t, IEventListener *l);
void removeListener(IEvent::EventType t, IEventListener *l);

private:
std::queue<std::unique_ptr<IEvent>> m_eventBus;
std::map<IEvent::EventType, std::set<IEventListener *>> m_routingTable;
};

EventBus.cpp

#include "EventBus.h"

using namespace std;/**
* Gives the EventBus a chance to dispatch and route events
* Listener callbacks will be called from here
*/
void EventBus::update()
{
while (!m_eventBus.empty())
{
// Get the next event (e_local now owns the on-heap event object)
unique_ptr<IEvent> e_local(move(m_eventBus.front()));
m_eventBus.pop();

IEvent::EventType t = e_local->getType();
auto it = m_routingTable.find(t);
if (it != m_routingTable.end())
{
for (auto l : ((*it).second))
{
l->handleEvent(e_local.get());
}
}
}
}

/**
* Posts an event to the bus, for processing and dispatch later on
* NB: The event bus will takes ownership of the on-heap event here
*/
void EventBus::postEvent(unique_ptr<IEvent> &e)
{
// The EventBus now owns the object pointed to by e
m_eventBus.push(unique_ptr<IEvent>(move(e)));
}

/**
* Registers a listener against an event type
*/
void EventBus::registerListener(IEvent::EventType t, IEventListener *l)
{
// Add this listener entry
// If the routing table doesn't have an entry for t, std::map.operator[] will add one
// If the listener is alredy registered std::set.insert() won't do anything
m_routingTable[t].insert(l);
}

/**
* Removes a listener from the event routing table
*/
void EventBus::removeListener(IEvent::EventType t, IEventListener *l)
{
// Check if an entry for event t exists
auto keyIterator = m_routingTable.find(t);
if (keyIterator != m_routingTable.end())
{
// Remove the given listener if it exists in the set
m_routingTable[t].erase(l);
}
}

Как вы можете видеть, в моей текущей реализации я создаю конкретные реализации IEvent для каждого типа события, которое я хочу передать. Я сделал это так, чтобы к каждому событию могли быть прикреплены пользовательские данные (требование для моей ситуации). К сожалению, это означает, что моя система EventBus должна знать обо всех пользователях системы, увеличивая связь между моим классом EventBus и пользователями этого класса. Кроме того, интерфейс IEvent должен содержать список всех типов событий в виде перечисления, что имеет ту же проблему (повышенная связь).

  1. Есть ли способ изменить эту реализацию так, чтобы EventBus мог быть полностью универсальным (не нужно знать о пользователях EventBus), и все же позволить мне передавать пользовательские данные с каждым событием? Я посмотрел на C ++ 11 шаблонные функции с переменным числом, но не мог понять, как их использовать в этом случае.
  2. В качестве побочного вопроса, я использую std::unique_ptr правильно здесь?

1

Решение

Вопрос 1 «Есть ли способ изменить эту реализацию, чтобы EventBus мог быть полностью универсальным»:

Короткий ответ, да.

Более длинный ответ: Есть много способов сделать это. Один описан здесь:

Как производитель, так и потребитель мероприятия должны согласовать тип / данные, но EventBus Сам не должен знать. Одним из способов достижения этого может быть использование boost::signals2::signal<T> как тип события. Это даст вам проверенную, гибкую и безопасную реализацию типа сигнал / слот. Однако он не предоставляет возможности ставить в очередь обратные вызовы слотов и обрабатывать их из EventBus::update()-функции.

Но это также может быть исправлено. Делая тип события EventBus::postEvent() принимает в качестве параметра быть std::function<void()> и звонит postEvent() как это:

boost::signals2::signal<int> signal;
...
eventbus.postEvent(boost::bind(signal, 42));
// note: we need to use boost::bind (not std::bind) for boost::signals to be happy

EventBus увидим std::function<void()> и отправить в слот. Данные (42 в этом примере) будут сохранены в результате boost::bind и использоваться в качестве параметра при вызове слота.

вопрос 2 «Я использую std::unique_ptr правильно»:

Почти. Я бы бросил ссылку EventBus::postEvent делая это:

void EventBus::postEvent(std::unique_ptr<IEvent> e);

Делая это, вы заставляете абонента активно перемещать std::unique_ptr<IEvent> в EventBus, Это заставит пользователя осознать EventBus берет на себя ответственность, а также делает очевидным для людей, читающих код, каково намерение и как передается право собственности.

CppCoreGuidelines R.32:

«Возьмите параметр unique_ptr, чтобы выразить, что функция предполагает владение виджетом»

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]