私のアプリケーションでは、「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インターフェイスは、すべてのイベントタイプのリストを列挙型として保持する必要があります。これは同じ問題(カップリングの増加)があります。
- この実装を変更してEventBusを完全に汎用にすることができます(EventBusのユーザーについて知る必要はありません)が、各イベントでカスタムデータを渡せるようにする方法はありますか?私はC++ 11バリデーションテンプレート関数を調べましたが、この場合にはどのように使用するのか分かりませんでした。
- 副疑問として、
std::unique_ptr
を正しく使用していますか?短い答えはい、: