2016-06-28 74 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參數來表達一個函數假定一個小部件的所有權」