观察者模式在工程项目里具有非常广泛的应用,本文聊一聊自己对观察者模式的一些理解和实现。
按照我自己的风格,我非常不赞同讲各种原理看起来深入浅出头头是道最后就贴几句伪代码甚至伪代码都没有就完事的博客方式。既然写在博客上了,就是为了供他人讨论和借鉴。简而言之,上代码,讲道理。
最简单的观察者实现
#include <iostream>
#include <list>
using namespace std;
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
class Subject;
class IObserver
{
public:
virtual void OnTrigger(Subject *subject) = 0;
};
class Subject
{
typedef list<IObserver *>::iterator ListIter;
public:
void Atatch(IObserver *obs)
{
boost::mutex::scoped_lock lock(m_mutex);
m_ptrObservers.push_back(obs);
}
void Detach(IObserver *obs)
{
boost::mutex::scoped_lock lock(m_mutex);
ListIter it = find(m_ptrObservers.begin(), m_ptrObservers.end(), obs);
if (it != m_ptrObservers.end())
{
m_ptrObservers.erase(it);
}
}
void Trigger()
{
boost::mutex::scoped_lock lock(m_mutex);
for (ListIter it = m_ptrObservers.begin(); it != m_ptrObservers.end(); ++it)
{
(*it)->OnTrigger(this);
}
}
private:
boost::mutex m_mutex;
list<IObserver *> m_ptrObservers;
};
class Observer : public IObserver
{
public:
void OnTrigger(Subject *subject)
{
cout << "Observer:OnTrigger" << endl;
}
};
int main()
{
vector<IObserver *> vecObs;
Subject subject;
for (int i = 0; i < 10; ++i)
{
IObserver *ptrObs = new Observer();
subject.Atatch(ptrObs);
vecObs.push_back(ptrObs);
}
subject.Trigger();
{
// todo delete here
}
return 0;
}
当然这其中的问题是多方面的,显然,在Trigger里面,(*it)->OnTrigger(this),如果观察者在自己的实现中做了Detach或者Atach其他指针的操作,那么死锁是不可避免的了。因此最好使用递归锁:
boost::recursive_mutex m_mutex;
上锁时:
boost::recursive_mutex::scoped_lock lock(m_mutex);
这样是不是问题就解决了呢?显然问题仍然非常严重:由于Detach或者Atach操作修改了容器内的结构,因此在
for (ListIter it = m_ptrObservers.begin(); it != m_ptrObservers.end(); ++it)
{
(*it)->OnTrigger(this);
}
这段代码时,迭代器失效会直接导致程序的崩溃。
如何解决这样的问题,这里就需要一些技巧了。
计数机制避免迭代器失效
#include <iostream>
#include <list>
using namespace std;
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
class Subject;
class IObserver
{
public:
virtual void OnTrigger(Subject *subject) = 0;
};
class Subject
{
typedef list<IObserver *>::iterator ListIter;
public:
Subject()
{
m_nUseCount = 0;
}
void Atatch(IObserver *obs)
{
boost::recursive_mutex::scoped_lock lock(m_mutex);
++m_nUseCount;
if (m_nUseCount == 1)
{
m_ptrObservers.push_back(obs);
}
else
{
// 本类里面使用的list,push_back不会迭代器失效,但是如果使用vector等其他容器,就会存在问题了
// 这时候先放入缓冲区里面,防止Trigger处失效
m_ptrToAdd.push_back(obs);
}
--m_nUseCount;
}
void Detach(IObserver *obs)
{
boost::recursive_mutex::scoped_lock lock(m_mutex);
++m_nUseCount;
ListIter it = find(m_ptrObservers.begin(), m_ptrObservers.end(), obs);
if (it != m_ptrObservers.end())
{
if (m_nUseCount == 1)
m_ptrObservers.erase(it); // 说明只有当前处使用了m_ptrObservers,可以放心删除
else
*it = NULL;
}
--m_nUseCount;
}
void Trigger()
{
boost::recursive_mutex::scoped_lock lock(m_mutex);
++m_nUseCount;
// 先检查一下缓冲区
if (m_nUseCount == 1 && !m_ptrToAdd.empty())
{
for (ListIter it = m_ptrToAdd.begin(); it != m_ptrToAdd.end(); ++it)
{
m_ptrObservers.push_back(*it);
}
m_ptrToAdd.clear();
}
for (ListIter it = m_ptrObservers.begin(); it != m_ptrObservers.end(); )
{
if (*it)
{
(*it)->OnTrigger(this);
++it;
}
else
{
if (m_nUseCount == 1)
{
// 这时候确定可以删除了
m_ptrObservers.erase(it++);
}
}
}
--m_nUseCount;
}
private:
boost::recursive_mutex m_mutex;
list<IObserver *> m_ptrObservers;
list<IObserver *> m_ptrToAdd;
int m_nUseCount;
};
class Observer : public IObserver
{
public:
void OnTrigger(Subject *subject)
{
cout << "Observer:OnTrigger" << endl;
//subject->Detach(this);
//subject->Atatch(this);
}
};
int main()
{
vector<IObserver *> vecObs;
Subject subject;
for (int i = 0; i < 10; ++i)
{
IObserver *ptrObs = new Observer();
subject.Atatch(ptrObs);
vecObs.push_back(ptrObs);
}
subject.Trigger();
{
// todo delete here
}
return 0;
}
这一次,我们使用了一个UserCount作标记,用来标志当前有几处正在访问观察者列表,如果发现其他处在访问,那么尽量避免直接Add或者Erase一类会使迭代器失效的操作,等到有机会以遍历观察者列表并且只有当前处在访问该列表时,再做真正的增加或删除操作。
另外,还有更加精妙的方案来可以解决我们繁琐的使用UserCount自己进行++,–操作,那就是通过智能指针的机制实现Copy_On_Write-Ref_On_Read,打算再开一篇博文贴代码讲原理。