2016-06-28 6 views
1

私のアプリケーションでは、「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; 
}; 

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自体は知る必要はありません。これを達成する1つの方法は、イベントタイプとしてboost::signals2::signal<T>を使用することです。これにより、実績のある、柔軟でタイプセーフなシグナル/スロットの実装が可能になります。しかし、それが提供しないものは、スロットコールバックを待ち行列に入れ、EventBus::update() -functionから処理する可能性があります。

しかし、それはまた救済することができます。 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); 

これを行うことにより、あなたが積極的にEventBusstd::unique_ptr<IEvent>を移動するには、発信者を強制します。これにより、ユーザーにEventBusの所有権があることを知らせることができます。また、コードを読んでいる人に意図が何であり、所有権が移転されているのかが分かりやすくなります。

CppCoreGuidelines R.32

"機能は、ウィジェットの所有権を前提としていることを表現するためにunique_ptrをパラメータを取ります"
関連する問題