我寫了很多C++代碼,需要爲我正在開發的一些遊戲組件創建一個Observer。我需要一些東西來分發「開始框架」,「用戶輸入」等,作爲遊戲中的事件給感興趣的各方。
我也希望能夠處理更多事件的粒度。我有很多小的事情會發生......我不需要讓那些有興趣重新設置下一幀的部分擔心用戶輸入的更改。我也希望它是直的C++,不依賴於平臺或特定的技術(如boost,Qt等),因爲我經常構建和重用不同的組件(以及它們背後的想法)項目。
這裏是什麼,我想出了一個草圖作爲一種解決方案:
- 觀察員與鍵(枚舉值,而不是字符串單身,這是一個速度的權衡,因爲密鑰是不搜索散列,但它意味着沒有簡單的「字符串」名稱,並且您必須提前定義它們)以便主題註冊感興趣。因爲它是單例,它總是存在。
- 每個主題都來自一個共同的基類。基類有一個抽象的虛函數Notify(...),它必須在派生類中實現,還有一個析構函數,當它被刪除時,它會從Observer中移除它(它總是可以到達)。
- 在Observer本身內部,如果在通知(...)正在進行時調用Detach(...),則任何分離的主題最終都會列在列表中。
- 當通知(...)在觀察者上被調用時,它創建主題列表的臨時副本。當它遍歷它時,它將它與最近分離的進行比較。如果目標不在上面,則在目標上調用Notify(...)。否則,它會被跳過。
- Observer中的Notify(...)還會跟蹤處理級聯調用的深度(A通知B,C,D和D.Notify(...)觸發Notify(...)調用以E等)
這是接口告終什麼看起來像:
/*
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 Notiifer for enumerated events.
Notifier derived classes MUST implement the notify function, which has
a prototype of:
void Notify(const NOTIFIED_EVENT_TYPE_T& event)
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;
注:公告類有一個單一的功能,通知(......)在這裏。因爲void *的不是類型安全的,我創建的其他版本,其中相同的通知長相:
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, int value);
virtual void Notify(Notifier::NOTIFIED_EVENT_TYPE_T eventType, const string& str);
相應的通知(...)方法添加到通知本身。所有這些都使用單個函數來獲取「目標列表」,然後在目標上調用相應的函數。這很好地工作,並保持接收器不必做醜陋的演員。
這似乎工作得很好。該解決方案與源代碼一起發佈在網絡here上。這是一個相對較新的設計,所以任何反饋都非常感謝。
'Subject'不應該知道哪個觀察者對哪個'Subject'屬性感興趣。每個'ConcreteObserver'都知道'ConcreteSubject'的屬性是什麼,'ConcreteSubject'應該有public getters,所以'ConcreteObserver'可以獲取這些屬性的最新值(當'ConcreteSubject'觸發事件或調用時'Subject :: Notify ()'方法 - Google for Gang Of Four Observer實現)。 'registerObservers'應該只是添加一個新的'ConcreteObserver'到Observers列表中。你可以用多個'ConcreteSubject'來註冊每個'ConcreteObserver'。 –
@BojanKomazec「公共獲得者」是什麼意思?請解釋。當主題調用「notify」函數時,會發生什麼? –
我的意思是公共存取方法。查看下面的AquilaRapax的答案,並查找'WheatherData :: getTemperature()','WheatherData :: getHumidity()'等''Notify()'遍歷所有註冊的觀察者列表並調用'Update()'。每個'ConcreteObserver'都實現'Update()',並且在這個方法內部通過這些getter獲得'ConcreteSubject'屬性的最新值。 –