使用Observer模式实现事件的触发、响应机制
最近使用C++进行工作,希望能有一种比较简便易用的消息响应机制,于是想到了observer模式。
实现过程还是比较简单的,如下:
观察者对观察对象提出监听的请求,观察对象将观察者放入自己的监听列表中。当事件发生的时候,观察对象从监听列表中取得观察者的指针一一通知他们。观察者得到这个通知,进行自己的事件响应。使用接口的形式隔离观察者和观察对象之间的直接关联。
可以参见这篇文章:
http://www.cnblogs.com/lane_cn/articles/73240.html
在程序中我是这样实现的:
为了简化客户的调用,我把事件的发起者和捕获者合并成了同一个类,取名叫做CEventHandler,CEventHandler的定义如下:
class CEventHandler
![]()
![]()
{
public:
//! 析构函数
virtual ~CEventHandler(void);
//! 得到事件通知
virtual void ReceiveEvent(CEventHandler* pSender, int nEventId, void* pArgs)
![]()
{
return;
};
//! 注册一个观察对象
void AddListener(CEventHandler* pListener);
//! 注销一个观察对象
void RemoveListener(CEventHandler* pListener);
protected:
CEventHandler(void);
//! 通知所有的观察者
void Notify(int nEventId, void* pArgs);
private:
int m_nListenerCount; //!< 观察者的数量
};
AddListener和RemoveListener分别是注册事件监听和注销事件监听的方法,当事件发生的时候,使用Notify通知外界,子类可以重载ReceiveEvent方法捕获事件并且进行相应。
ReceiveEvent没有定义为纯虚函数,这就意味着,子类也可以不捕捉事件。这样符合事件响应机制的原则。
程序实现片断:
void CEventHandler::AddListener(CEventHandler* pListener)
![]()
![]()
{
if (m_nListenerCount + 1 > m_nListSize)
![]()
{
//先把队列的长度延长一倍
DoubleMemorySize();
}
//Listener加在队列的最后
m_pListenerList[m_nListenerCount] = pListener;
m_nListenerCount ++;
}
void CEventHandler::Notify(int nEventId, void* pArgs)
![]()
![]()
{
for (int i=0; i<m_nListenerCount; i++)
![]()
{
if (m_pListenerList[i] != 0x00)
![]()
{
m_pListenerList[i]->ReceiveEvent(this, nEventId, pArgs);
}
}
}
『实现的过程中使用一个CEventHandler*数组来保存监听者的列表,数组的扩大和缩小是自己处理的,为了避免频繁的内存重新分配,影响系统效率,在下标越界的情况下将数组的长度延长一倍,多占一些内存。这种方式模仿了标准模板类中很多集合类的内存管理策略。』
使用的时候,象下面这样:
//! 事件的发起者
class Cast : public CEventHandler
![]()
![]()
{
public:
static const int EVENT_SOMETHING = 101; //定义一个事件ID
//在这个方法中抛出了一个事件
void SomethingHappen()
![]()
{
//
//调用Nodify方法,触发一个事件,包括事件ID和消息的参数<br>
Nodify(this, EVENT_SOMETHING, pArgs);
}
};
//! 事件的监听者,在监听者中重载ReceiveEvent方法
class Listener : public CEventHandler
![]()
![]()
{
public:
Listener()
![]()
{
m_pCast = new Cast();
m_pCast->AddListener(this); //将自己加到事件触发者的监听队列中
};
private:
Cast* m_pCast;
//事件响应函数,一般情况下定义为private
virtual void ReceiveEvent(CEventHandler* pSender, int nEventId, void* pArgs)<br>
![]()
{
//捕获事件,判别事件ID,对其进行处理
//
if (Cast::EVENT_SOMETHING == nEventId)
![]()
{
//
处理Cast抛出的一个事件
}
}
};
在C++的实现中出现了内存问题
这样的程序我在Java中曾经使用过,但是在新的环境下却遇到了问题,例如下面的情况:
Listener* listener = new Listener();
Cast* cast = new Cast();
cast->AddListener(listener);//将listener加入cast的监听列表中
//
程序执行过程
delete listener;//释放listener
在释放listener之后,listener的指针却仍然存在于cast的监听列表中,并且指针仍然指向原先的位置,当cast对象抛出一个事件,寻找所有的监听者一一通知的时候就遇到了错误,这个listener已经是一个无效的指针了。产生这个错误的原因有下面三个:
1、释放listener指针之前,没有先将listener从cast的监听者中去除;
cast->RemoveListener(listener);
delete listener; 2、程序员没有按照编程规约的要求,在释放一个指针之后将其置为空,影响了程序的判断;
delete listener;
listener = 0x00; 3、这个事件响应的机制是有缺陷的。可以在CEventHandler中加入下面的机制:
当listener加入cast的监听者的时候,cast也将自己加入listener的监听者,当listener被析构的时候,采用特别的方式发出一个LISTENER_DELETED事件,cast的基类捕获这个事件,将listener从自己的监听者中去掉。用这样的方式就可以彻底的解决这个问题。
问题的解决 最后在实际的工程中我是用了1和2的办法,个人觉得这样已经可以满足需要,并且迫使程序员更加严格的按照编程规约进行工作。
在C++的世界中真是什么事情都要自己来做。事实上以前用Java或者C#实现的上述功能的时候就是有问题的,没有及时的回收资源,只是由于程序中事件运用的不复杂,加上这些语言的内存是由虚拟机管理,占用过多以后会由虚拟机自动释放,才使得问题没有暴露。