C++ 如何减少事件总线实现中的耦合

C++ 如何减少事件总线实现中的耦合,c++,events,decoupling,C++,Events,Decoupling,在我的应用程序中,我有几个模块不符合“is-a”或“has-a”关系,但仍然需要相互通信和传递数据。为了尝试松散地耦合这些模块,我实现了一个事件总线类,该类处理从“事件海报”到“事件侦听器”的消息传递 如果类希望注册以接收某些事件,则可以实现IEventListener。同样,如果类需要将事件推送到总线上,它们可以调用EventBus::postEvent()。当调用EventBus::update()时,EventBus处理调度消息队列并将它们路由到注册的侦听器 EventBus.h #pra

在我的应用程序中,我有几个模块不符合“is-a”或“has-a”关系,但仍然需要相互通信和传递数据。为了尝试松散地耦合这些模块,我实现了一个事件总线类,该类处理从“事件海报”到“事件侦听器”的消息传递

如果类希望注册以接收某些事件,则可以实现
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;
};
#pragma一次
#包括
#包括
#包括
#包括
I类事件
{
公众:
静态枚举事件类型
{
敌人死了,
伊夫•敌人•孕育,
游戏结束了
};
虚拟~IEvent(){};
虚拟事件类型getType()常量=0;
};
类IEventListener
{
公众:
虚空handleEvent(IEEvent*const e)=0;
};
类事件总线
{
公众:
EventBus(){};
~EventBus(){};
无效更新();
事件后无效(标准:唯一的ptr&e);
void registerListener(IEvent::EventType t,IEventListener*l);
void removeListener(IEvent::EventType t,IEventListener*l);
私人:
std::队列m_事件总线;
标准::地图m_路由表;
};
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);
    }
}
#包括“EventBus.h”
使用名称空间std;
/**
*使EventBus有机会分派和路由事件
*将从此处调用侦听器回调
*/
void EventBus::update()
{
而(!m_eventBus.empty())
{
//获取下一个事件(e_local现在拥有堆上事件对象)
唯一的_ptr e_local(move(m_eventBus.front());
m_eventBus.pop();
IEEvent::EventType t=e_local->getType();
自动it=m_routingTable.find(t);
if(it!=m_routingTable.end())
{
对于(自动l:(*it.second))
{
l->handleEvent(e_local.get());
}
}
}
}
/**
*将事件发布到总线,以便稍后处理和调度
*注意:这里,事件总线将拥有堆上事件的所有权
*/
void EventBus::postEvent(唯一的\u ptr&e)
{
//EventBus现在拥有e指向的对象
m_eventBus.push(唯一的_ptr(移动(e));
}
/**
*针对事件类型注册侦听器
*/
void EventBus::registerListener(IEvent::EventType t,IEventListener*l)
{
//添加此侦听器条目
//如果路由表没有t的条目,std::map.operator[]将添加一个
//如果侦听器已注册,则std::set.insert()不会执行任何操作
m_routingTable[t]。插入(l);
}
/**
*从事件路由表中删除侦听器
*/
void EventBus::removeListener(IEvent::EventType t,IEventListener*l)
{
//检查事件t的条目是否存在
自动键迭代器=m_routingTable.find(t);
if(keyIterator!=m_routingTable.end())
{
//如果给定的侦听器在集合中存在,请将其删除
m_routingTable[t]。擦除(l);
}
}
如您所见,在我当前的实现中,我为我想要传递的每种类型的事件创建了具体的IEvent实现。我这样做是为了每个事件都可以附加自定义数据(这是我的情况所必需的)。不幸的是,这意味着我的EventBus系统必须了解系统的所有用户,这增加了我的EventBus类和该类用户之间的耦合。此外,IEvent接口需要将所有事件类型的列表保存为一个枚举,这也有同样的问题(耦合增加)

  • 是否有一种方法可以修改此实现,使EventBus完全通用(不需要了解EventBus的用户),但仍然允许我在每个事件中传递自定义数据?我研究了C++11可变模板函数,但不知道在这种情况下如何使用它们
  • 作为一个附带问题,我在这里是否正确使用了
    std::unique\u ptr

  • 问题1是否有办法修改此实现,使EventBus完全通用:

    简短的回答,是的

    更详细的回答:有很多方法可以做到这一点。这里描述了一个例子:

    事件的生产者和消费者都需要就类型/数据达成一致,但是
    EventBus
    本身不需要知道。实现这一点的一种方法是使用
    boost::signals2::signal
    作为事件类型。这将为您提供经验证、灵活且类型安全的信号/插槽实现。但是,它不会提供将插槽回调排队并从
    EventBus::update()
    -函数处理它们的可能性

    但是,这也是可以补救的。通过将事件类型
    EventBus::postEvent()
    作为参数be
    std::function
    并调用
    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
    
    通过这样做,您可以强制调用方主动将
    std::unique_ptr
    移动到
    EventBus
    中。这将使用户意识到
    EventBus
    拥有所有权,并使阅读代码的人清楚地知道意图是什么以及所有权是如何转移的

    :

    “使用唯一的_ptr参数表示函数拥有小部件的所有权”

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