目录
模式简介
介绍
优点
缺点
代码实现
场景说明
实现代码
运行结果
模式简介
观察者模式(Observer Pattern
),也叫我们熟知的发布-订阅模式。
它是一种行为型模式。
介绍
观察者模式主要关注的是对象的一对多的关系,
也就是多个对象依赖于一个对象,当该对象的状态发生改变
时,其他对象都能够收到相应的通知
意图:
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。主要解决:
一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。如何解决:
使用面向对象技术,可以将这种关系弱化。
优点
- 观察者和被观察者是抽象耦合的;
- 建立了一套触发机制。
缺点
- 如果一个被观察者对象有很多的直接和间接的观察者的话,通知所有的观察者需要花费很长的时间;
- 如果在观察者和被观察者目标之间有循环依赖的话,观察目标会触发他们之间的循环调用,可能会导致系统崩溃;
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅是知道观察目标发生了变化。
代码实现
场景说明
我们现在有三个观察者和三种消息,这三个观察者分别对不同的消息感兴趣
我们实现的话,可以简单的设置三个观察者,一个主题类(被观察者
)。
这三个观察者可以设置自己喜欢的、感兴趣的消息类型(1、2、3
)。
他们处理收到的消息就是打印一下自己收到了什么消息。
具体实现如下
实现代码
/*observer1 observer2 observer3Subject (主题)主题更改,应该及时通知相应的观察者,去处理相应的事件
*/
class Observer // 观察者抽象类
{
public://处理消息的接口virtual void handle(int msgid) = 0;
};//第一个观察者实例
class Observer1 : public Observer
{
public:void handle(int msgid){switch (msgid){case 1:cout << "Observer1 recv 1 msg" << endl;break;case 2:cout << "Observer1 recv 2 msg" << endl;break;default:cout << "Observer1 recv unkonw msg!" << endl;break;}}
};
//第二个观察者实例
class Observer2 : public Observer
{
public:void handle(int msgid){switch (msgid){case 2:cout << "Observer2 recv 2 msg" << endl;break;default:cout << "Observer2 recv unkonw msg!" << endl;break;}}
};
//第三个观察者实例
class Observer3 : public Observer
{
public:void handle(int msgid){switch (msgid){case 1:cout << "Observer3 recv 1 msg" << endl;break;case 3:cout << "Observer3 recv 3 msg" << endl;break;default:cout << "Observer3 recv unkonw msg!" << endl;break;}}
};//主题类
class Subject
{
public://给主题增加观察者对象void addObserver(Observer* obser,int msgid){_subMap[msgid].push_back(obser);}//主题检测发生改变,通知相应的观察者对象处理事件void dispatch(int msgid){auto it = _subMap.find(msgid);if (it != _subMap.end()){//通过多态,实现不同的指向for (Observer* pObser : it->second){pObser->handle(msgid);}}}
private://用来保存订阅的消息unordered_map<int, list<Observer*>> _subMap;
};
我们可以看到主题类(Subject
)的数据成员是一个unordered_map。使用这个是因为我们不需要数据是有序的,为了提高增删查的速率
,使用了无序
map。
使用map的好处是,它作为一个键值对
,可以存储我们想要的数据类型:(消息类型,订阅此消息类型的观察者们)。
并且,在主题类(Subject
)的成员方法addObserver
中,我们使用了一个中括号运算符([]
)重载的特性:
如果当前容器中存有相应的msgid
键的话,就直接添加对应的值(Obser
);
如果当前容器中没有相应的msgid
键的话,就直接添加该键,并且添加一个默认的值。
运行结果
我们使用如下的代码:
void main()
{Subject subject;Observer* p1 = new Observer1();Observer* p2 = new Observer2();Observer* p3 = new Observer3();subject.addObserver(p1, 1);subject.addObserver(p1, 2);subject.addObserver(p2, 2);subject.addObserver(p3, 1);subject.addObserver(p3, 3);int msgid = 0;for (;;){cout << "请输入消息id:" << endl;cin >> msgid;if (msgid == -1)break;subject.dispatch(msgid);//发起通知}
}
运行结果如下