什么是观察者模式?在回答这个问题之前,先看看这样一个场景。这段时间,疫情在全世界肆虐,很多人每天都会习惯性地用手机看数据的变化,数据有湖北省内外的对比,新增、死亡、治愈的对比,国内外的对比等等。根据数据的变化,手机的统计界面都会用曲线图或者饼状图呈现,然后用户就能很直观地看到数据的变化。其实这就是观察者模式的一个常见应用。
定义
“观察者模式(又被称为发布-订阅(Publish/Subscribe)模式),属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。”
解释一下,在前面的例子中,疫情数据可以看作主题对象,而统计界面呈现就是观察者。比如一个主题对象是意大利每天新增新冠感染人数,通过这个数据,统计界面可以显示两个曲线图,一个是每日新增曲线图,还有一个是累计感染人数曲线图,这两个曲线图会根据主题对象的变化而变化,依赖于同一个数据。当然,疫情数据远不止一个,这里只是将它简化有助于理解。
框架图
(1)Subject:抽象主题。它维护了一个观察者集合,可以有任意数量的观察者,抽象主题提供接口,可以增加和删除观察者对象。当被观察的数据发生变化时,通知所有的观察者更新自己。
(2)ConcreteSubject:抽象主题的具体实现。当被观察的数据发生变化时,通知所有的观察者更新自己。若这个主题表示某一个疫情数据,当疫情数据变化时,通知相应的统计图更新。
(3)Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
(4)ConcrereObserver:抽象观察者的具体实现,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
为什么要要用到抽象类,实现类?其实是增加代码的复用性和降低耦合性,有利于后期的维护与拓展。抽象类定义了被观察者与观察者之间的“变化转移”,同时建立了一套触发机制,它将大的框架搭好了,根据需求的变化,如何更新与拓展,就由程序员自由发挥了。
例子
通过一个简单的例子来加深理解。
1、抽象观察者
#pragma once
class Watcher{
public:
virtual void dosomething(int a) = 0;
};
2、具体观察者
定义了两个观察者Watcher1,Watcher2,它们的区别在于对数据的处理方式。
#pragma once
#include "Watcher.h"
#include <iostream>
class Watcher1 : public Watcher{
public:
virtual void dosomething(int data) override{
cout << "Watcher1:: " << data << endl;
}
};
#pragma once
#include "Watcher.h"
#include <iostream>
class Watcher2 : public Watcher{
public:
virtual void dosomething(int data) override{
cout << "Watcher2:: " << data << endl;
}
};
3、抽象主题
#pragma once
#include <list>
#include "Watcher.h"
class topic{
public:
virtual void insertwach(Watcher*) = 0;
virtual void remvovewatch(Watcher*) = 0;
virtual void notify() = 0;
list<Watcher*> m_watcher;
};
4、主题的实现
#pragma once
#include <list>
#include <windows.h>
#include "topic.h"
#include "Watcher.h"
class mytopic : public topic{
public:
mytopic(int d) :
m_data(d){}
virtual void insertwach(Watcher* w) override{
m_watcher.push_back(w);
}
virtual void remvovewatch(Watcher* w) override{
m_watcher.remove(w);
}
virtual void notify() override{
for (auto i : m_watcher){
i->dosomething(m_data);
}
}
void setdata(int data){
int temp = m_data;
m_data = data;
if (temp != m_data){
notify();
}
}
private:
int m_data;
};
当主题数据产生变化时,通知Watcher更新。
5、main函数
#include "mytop.h"
#include "Watcher1.h"
#include "Watcher2.h"
void main(){
Watcher* myw1 = new Watcher1();
Watcher* myw2 = new Watcher2();
topic* myt = new mytopic(0);
myt->insertwach(myw1);
myt->insertwach(myw2);
for (int i = 0; i < 10; ++i){
dynamic_cast<mytopic*>(myt)->setdata(i); //指向基类指针需要强转调用派生类定义的方法
}
return;
}
6、运行结果
可以看到,传入的int数据,两个观察者用不同的方法在控制台显示。
后记
1、使用面向对象的抽象,观察者模式使得我们可以独立地改变主题和观察者,使两者的依赖关系达到松耦合;
2、主题发送通知时,无需指定观察者,通知会自动传播;
3、观察者自己决定是否需要订阅通知,主题对象对此一无所知;
4、观察者模式是基于事件的UI框架中常用设计模式,也是MVC模式的一个重要组成部分。