观察者模式,也有叫发布-订阅模式的。这是个人比较喜欢的设计模式之一。因为它比较容易使用,而且效果还挺好。
意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
结构
其实,观察者模式里面目标对象和观察者对象还是松耦合的。目标对象需要知道观察者的一些比较固定的接口,比如Update(),同时观察者也需要知道目标对象的一些状态,因为观察者需要根据目标对象的状态信息来更新。
IM通讯软件里面,比如qq,当你的好友发一条信息给你的时候,屏幕右下角的qq图标会不停的跳动。在这个例子里面,qq肯定会有一个接收网络数据的对象,这就是个目标对象,同时右下角的图标是个系统托盘对象,也可以理解为观察者对象。当然我并不知道qq是否这么做的,但是我感觉是可以使用观察者模式的。当网络对象接收到一条信息的时候,就可以更新右下角的系统托盘对象,将图标动起来。
这里整一个比较简单的例子,假设我们写了一个通讯软件的客户端。每当收到一条信息的时候,就需要将这条信息显示到对话框上。
先定义目标对象基类
class CSubject
{
public:
virtual const string& GetMsg() = 0;
virtual void Attach(CObserver* observer) = 0;
virtual void Notify() = 0;
};
注意其中的Attach函数,这个函数就是将一个观察者对象保存到目标对象里面。这样目标对象就可以更新观察者对象。
再定义一个观察者对象基类
class CObserver
{
public:
virtual void Update(CSubject* subject) = 0;
};
只有一个函数Update(),这个函数将被目标对象调用。
再看看各自的子类:
首先是网络接收对象
class CReceiver: public CSubject
{
public:
const string& GetMsg(){return m_msg;}
//实现增加观察者函数
void Attach(CObserver* observer)
{
m_listObservers.push_back(observer);
}
//通知所有的观察者更新数据
void Notify()
{
BOOST_FOREACH(CObserver* observer, m_listObservers)
{
observer->Update(this);
}
}
//模拟接收到一条信息
void ReceivedMsg(const string& msg)
{
m_msg = msg;
Notify();//更新观察者。
}
protected:
list<CObserver*> m_listObservers;
string m_msg;
};
每当收到一条信息的时候,就调用Notify函数来更新所有的观察者。
观察者的实现:
class CMsgDialog: public CObserver
{
public:
virtual void Update(CSubject* subject)
{
cout << "show msg: " << subject->GetMsg() << "\n";
}
};
将目标对象的内容显示出来。
简单调用例子
void Pattern_Observer()
{
//模拟生成一个接收数据对象,这是一个目标对象
CReceiver* receiver = new CReceiver();
CMsgDialog* dlg = new CMsgDialog();
//将观察者attach到目标对象
receiver->Attach(dlg);
//模拟接收对象收到一条信息
receiver->ReceivedMsg("dinner together\n");
}
当接收对象收到一条信息的时候(这里是模拟的)receiver->ReceivedMsg("dinner together\n");
观察者对象将会自动被更新。
这个就是一个最最简单的观察者模式例子。其实这里会有个问题,目标对象将所有的观察者对象都更新了一下。这个就是所谓的推模型。目标对象不管观察者是否需要更新,都直接更新它们。那么假如有些观察者不需要更新呢?就可以考虑拉模型。拉模型就是由观察者主动通知目标对象,然后目标对象再更新观察者(观察者怎么知道什么时候可以更新了呢?这是一个问题)。
个人感觉观察者模式还是蛮有用的,效果还是可以的。但是这里有个问题,就是目标对象和观察者之间有一定程度的耦合。尽管我们可以降低耦合程度,但是毕竟还是避免不了耦合。