「ボックス」が「b1」を引数として「BOX_AREA_CHANGED」のように、システム内の単一の「イベント」を監視している場合、必要に応じてすべてのボックスが独自のエリアを変更できるようです。
したがって、これは「オブザーバー」パターンのように感じます。問題は、すべての参加者 (ボックス) が作成/破棄されたときに、必要に応じてアタッチ/デタッチすることです。また、特定のイベントに粒度で登録して、アクションを簡単にターゲットにできるようにする必要があります。すべてのボックスが "BOX_AREA_CHANGED" に応答し、"BOX_MOVED" に応答するのは少数のボックスのみである場合、後者のメッセージが発生した変更であるとき、前者が応答することは望ましくありません。
私は Notifier と呼ばれるこのようなものをまとめました。その基本的な要点は次のとおりです。
- オブザーバーは、サブジェクトが関心を登録するためのキー (文字列ではなく列挙値) を持つシングルトンです。シングルトンであるため、常に存在します。
- 各サブジェクトは、共通の基本クラスから派生します。基本クラスには、派生クラスで実装する必要がある抽象仮想関数 Notify(...) と、削除時に Observer (常に到達できる) からそれを削除するデストラクタがあります。
- Observer 自体の内部で、Notify(...) の進行中に Detach(...) が呼び出された場合、切り離されたすべての Subject がリストに追加されます。
- Observer で Notify(...) が呼び出されると、サブジェクト リストの一時的なコピーが作成されます。それを反復するとき、それを最近デタッチされたものと比較します。ターゲットがその上にない場合、Notify(...) がターゲット上で呼び出されます。それ以外の場合はスキップされます。
- オブザーバーの Notify(...) は、カスケード呼び出しを処理する深さも追跡します (A は B、C、D に通知し、D.Notify(...) は E への Notify(...) 呼び出しをトリガーします。等。)
これは、インターフェイスが次のようになったものです。
/*
The Notifier is a singleton implementation of the Subject/Observer design
pattern. Any class/instance which wishes to participate as an observer
of an event can derive from the Notified base class and register itself
with the Notifier for enumerated events.
Notifier derived classes MUST implement the notify function, which has
a prototype of:
void Notify(const NOTIFIED_EVENT_TYPE_T& event, void* data)
This is a data object passed from the Notifier class. The structure
passed has a void* in it. There is no illusion of type safety here
and it is the responsibility of the user to ensure it is cast properly.
In most cases, it will be "NULL".
Classes derived from Notified do not need to deregister (though it may
be a good idea to do so) as the base class destructor will attempt to
remove itself from the Notifier system automatically.
The event type is an enumeration and not a string as it is in many
"generic" notification systems. In practical use, this is for a closed
application where the messages will be known at compile time. This allows
us to increase the speed of the delivery by NOT having a
dictionary keyed lookup mechanism. Some loss of generality is implied
by this.
This class/system is NOT thread safe, but could be made so with some
mutex wrappers. It is safe to call Attach/Detach as a consequence
of calling Notify(...).
*/
class Notified;
class Notifier : public SingletonDynamic<Notifier>
{
public:
typedef enum
{
NE_MIN = 0,
NE_DEBUG_BUTTON_PRESSED = NE_MIN,
NE_DEBUG_LINE_DRAW_ADD_LINE_PIXELS,
NE_DEBUG_TOGGLE_VISIBILITY,
NE_DEBUG_MESSAGE,
NE_RESET_DRAW_CYCLE,
NE_VIEWPORT_CHANGED,
NE_MAX,
} NOTIFIED_EVENT_TYPE_T;
private:
typedef vector<NOTIFIED_EVENT_TYPE_T> NOTIFIED_EVENT_TYPE_VECTOR_T;
typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T> NOTIFIED_MAP_T;
typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T>::iterator NOTIFIED_MAP_ITER_T;
typedef vector<Notified*> NOTIFIED_VECTOR_T;
typedef vector<NOTIFIED_VECTOR_T> NOTIFIED_VECTOR_VECTOR_T;
NOTIFIED_MAP_T _notifiedMap;
NOTIFIED_VECTOR_VECTOR_T _notifiedVector;
NOTIFIED_MAP_ITER_T _mapIter;
// This vector keeps a temporary list of observers that have completely
// detached since the current "Notify(...)" operation began. This is
// to handle the problem where a Notified instance has called Detach(...)
// because of a Notify(...) call. The removed instance could be a dead
// pointer, so don't try to talk to it.
vector<Notified*> _detached;
int32 _notifyDepth;
void RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& orgEventTypes, NOTIFIED_EVENT_TYPE_T eventType);
void RemoveNotified(NOTIFIED_VECTOR_T& orgNotified, Notified* observer);
public:
virtual void Reset();
virtual bool Init() { Reset(); return true; }
virtual void Shutdown() { Reset(); }
void Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
// Detach for a specific event
void Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
// Detach for ALL events
void Detach(Notified* observer);
/* The design of this interface is very specific. I could
* create a class to hold all the event data and then the
* method would just have take that object. But then I would
* have to search for every place in the code that created an
* object to be used and make sure it updated the passed in
* object when a member is added to it. This way, a break
* occurs at compile time that must be addressed.
*/
void Notify(NOTIFIED_EVENT_TYPE_T, const void* eventData = NULL);
/* Used for CPPUnit. Could create a Mock...maybe...but this seems
* like it will get the job done with minimal fuss. For now.
*/
// Return all events that this object is registered for.
vector<NOTIFIED_EVENT_TYPE_T> GetEvents(Notified* observer);
// Return all objects registered for this event.
vector<Notified*> GetNotified(NOTIFIED_EVENT_TYPE_T event);
};
/* This is the base class for anything that can receive notifications.
*/
class Notified
{
public:
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const void* eventData) = 0;
virtual ~Notified();
};
typedef Notifier::NOTIFIED_EVENT_TYPE_T NOTIFIED_EVENT_TYPE_T;
注: Notified クラスには、ここでは Notify(...) という 1 つの関数があります。void* はタイプ セーフではないため、notify が次のような別のバージョンを作成しました。
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, int value);
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const string& str);
対応する Notify(...) メソッドが Notifier 自体に追加されました。これらはすべて、単一の関数を使用して「ターゲットリスト」を取得し、ターゲットで適切な関数を呼び出しました。これはうまく機能し、レシーバーが醜いキャストを行う必要がなくなります。
これはうまくいくようです。このソリューションは、ソース コードとともにWeb に掲載されています。これは比較的新しいデザインなので、フィードバックは大歓迎です。