2016-06-28 48 views
1

在我的应用程序中,我有几个模块不适合'is-a'或'has-a'关系,但仍需要通信并将数据传递给每个模块其他。为了尝试松散地耦合这些模块,我实现了一个Event Bus类,它处理从'event posters'到'event listeners'的消息传递。如何在事件总线实现中减少耦合

如果他们希望注册接收某些事件,类可以实现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 variadic模板函数,但无法弄清楚在这种情况下如何使用它们。
  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参数来表达一个函数假定一个小部件的所有权”