我正在实现类似于多线程俄罗斯方块游戏的观察者设计模式的机制。有一个Game类,它包含一组EventHandler对象。如果一个类想要将自己注册为Game对象的监听器,那么它必须继承Game :: EventHandler类。在状态更改事件上,在每个侦听器的EventHandler接口上调用相应的方法。这就是代码的样子:
class Game
{
public:
class EventHandler
{
public:
EventHandler();
virtual ~EventHandler();
virtual void onGameStateChanged(Game * inGame) = 0;
virtual void onLinesCleared(Game * inGame, int inLineCount) = 0;
private:
EventHandler(const EventHandler&);
EventHandler& operator=(const EventHandler&);
};
static void RegisterEventHandler(ThreadSafe<Game> inGame, EventHandler * inEventHandler);
static void UnregisterEventHandler(ThreadSafe<Game> inGame, EventHandler * inEventHandler);
typedef std::set<EventHandler*> EventHandlers;
EventHandlers mEventHandlers;
private:
typedef std::set<Game*> Instances;
static Instances sInstances;
};
void Game::RegisterEventHandler(ThreadSafe<Game> inGame, EventHandler * inEventHandler)
{
ScopedReaderAndWriter<Game> rwgame(inGame);
Game * game(rwgame.get());
if (sInstances.find(game) == sInstances.end())
{
LogWarning("Game::RegisterEventHandler: This game object does not exist!");
return;
}
game->mEventHandlers.insert(inEventHandler);
}
void Game::UnregisterEventHandler(ThreadSafe<Game> inGame, EventHandler * inEventHandler)
{
ScopedReaderAndWriter<Game> rwgame(inGame);
Game * game(rwgame.get());
if (sInstances.find(game) == sInstances.end())
{
LogWarning("Game::UnregisterEventHandler: The game object no longer exists!");
return;
}
game->mEventHandlers.erase(inEventHandler);
}
我经常遇到这种模式有两个问题:
我目前的解决方法是您可以在上面的代码示例中看到的。我使UnregisterEventHandler
成为一个静态方法,用于检查实例列表。这确实有帮助,但我发现这是一个有些过时的解决方案。
有没有人知道如何干净安全地实施通知者/监听系统的一套指导方针?关于如何避免上述陷阱的任何建议?
PS:如果您需要更多信息才能回答此问题,可以在此处在线查找相关代码:Game.h,Game.cpp,SimpleGame.h,SimpleGame.cpp,{ {3}}
答案 0 :(得分:1)
经验法则是对象的删除和新对象应该彼此靠近。例如。在构造函数和析构函数中,或在使用该对象的调用之前和之后。因此,当后者没有创建前一个对象时,删除另一个对象中的对象是一种不好的做法。
我不明白你是如何打包事件的。在处理事件之前,您似乎必须检查游戏是否仍然存在。或者您可以在活动和其他地方使用shared_ptr
,以确保最后删除游戏。
答案 1 :(得分:0)
我编写了很多C ++代码,需要为我正在处理的一些游戏组件创建一个Observer。我需要一些东西来分发“框架开始”,“用户输入”等,作为游戏中的事件给感兴趣的各方。我有同样的问题要考虑......事件的发射会导致另一个观察者的毁灭,这个观察者也可能随后开火。我需要处理这件事。我不需要处理线程安全,但我通常要求的设计要求是构建一些简单的(API智能),我可以在正确的位置放入一些互斥量,其余的应该自己处理。
我还希望它是直接的C ++,不依赖于平台或特定技术(例如boost,Qt等),因为我经常在不同的项目中构建和重用组件(以及它们背后的思想)
以下是我提出的解决方案的粗略草图:
接口最终看起来像这样:
/*
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 destrctor 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(...)。因为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本身。所有这些都使用单个函数来获取“目标列表”,然后在目标上调用适当的函数。这很好用,并使接收器不必做丑陋的演员。
这似乎运作良好。该解决方案与源代码一起发布在网络here上。这是一个相对较新的设计,因此非常感谢任何反馈。