设计模式之观察者模式(Observer)
·
前言
在学习侯捷老师的有关设计模式的课程(李建忠老师主讲)中,老师对23种设计模式的有自己的划分,如下。所以老师讲解是按照这种顺序讲解。

组件协作:

Observer
1.使用场合
- 为某些对象建立一种
“通知依赖关系”:即一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。 - 场景举例:
- 微信公众号,如果一个用户订阅了某个公众号,那么便会收到公众号发来的消息,那么,公众号就是『被观察者』,而用户就是『观察者』
- 气象站可以将每天预测到的温度、湿度、气压等以公告的形式发布给各种第三方网站,如果天气数据有更新,要能够实时的通知给第三方,这里的气象局就是『被观察者』,第三方网站就是『观察者』
- MVC模式中的模型与视图的关系也属于观察与被观察
以上场景举例来源自:链接
2.示例1:文件分割进度显示
假设我们要将一个大文件分割成几个小文件分别存储,并且在分割过程中实时显示分割的进度。这里就可以使用观察者模式,观察者就是进度显示的类,被观察者就是文件分割类。
观察者可以有多个,被观察者改变时需要通知哪些观察者是客户程序决定的。所以被观察者应该有方法删除和增加需要通知的观察者。
#include <iostream>
#include <list>
#include<string>
using namespace std;
//进度显示接口
class IProgress{
public:
virtual void DisplayProgress(float value) = 0;
virtual ~IProgress(){}
};
//文件分割类
class FileSplitter
{
private:
string m_filePath;
int m_fileNumber;
list<IProgress*> m_iprogresslist; // 抽象通知机制,支持多个观察者
public:
FileSplitter(const string& filePath, int fileNumber)
:m_filePath(filePath),m_fileNumber(fileNumber)
{}
void split()
{
//1.读取大文件
cout << "读取文件大小....." << endl;
//2.分批次向小文件中写入
cout << "拆分文件,写入小文件大小....." << endl;
for (int i = 0; i < m_fileNumber; i++){
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress( progressValue );//发送通知
}
}
//增加观察者
void addIProgress(IProgress* iprogress)
{
m_iprogresslist.push_back(iprogress);
}
//删除观察者
void removeIProgress(IProgress* iprogress)
{
m_iprogresslist.remove(iprogress);
}
protected:
//通知观察者
virtual void onProgress(float value)
{
list<IProgress*>::iterator itor = m_iprogresslist.begin();
while (itor != m_iprogresslist.end()){
(*itor)->DisplayProgress(value); //观察者的"更新状态"的虚方法
itor++;
}
}
};
//百分比进度显示
class PercentageProgress :public IProgress
{
public:
virtual void DisplayProgress(float value)
{
cout << "文件分割了" << value * 100 << "%" << endl;
}
};
//模拟窗口程序
class MainForm : public IProgress
{
string filePath;
int FileNumber;
public:
void Button1_Click(){//点击图形界面上的分割按钮时的回调函数
string filePath = "/C::";//模拟文件路径
int FileNumber = 10;//模拟文件数量
FileSplitter splitter(filePath, FileNumber);//1.生成文件分割器
//2.分割进度显示类,需加入被通知的列表中
PercentageProgress pp;//2.1 一个百分比进度显示类
splitter.addIProgress(&pp);//2.2百分比进度显示 订阅通知
splitter.addIProgress(this); //2.3进度条进度形式 订阅通知
//3.开始分割文件
splitter.split();//分割文件
}
virtual void DisplayProgress(float value){
cout << "进度条\t完成" << value << endl;
}
};
int main()
{
MainForm mf;//生成窗口
mf.Button1_Click();//模拟窗口点击
system("pause");
return 0;
}
当然,你可能会这样做:让文件分割类FileSplitter直接和进度显示类PercentageProgress组合。然后在文件分割时,直接通过PercentageProgress对象调用其方法显示进度。
#include <iostream>
#include <list>
using namespace std;
class PercentageProgress
{
public:
void DisplayProgress(float value)
{
cout << "显示文件分割百分比的方式." << value * 100 << "%" << endl;
}
};
class FileSplitter
{
private:
string m_filePath;
int m_fileNumber;
PercentageProgress m_percemtageprogress; //具体的对象
public:
FileSplitter(const string& filePath, int fileNumber)
:m_filePath(filePath), m_fileNumber(fileNumber)
{}
void split()
{
//1.读取大文件
cout << "读取文件大小....." << endl;
//2.分批次向小文件中写入
cout << "拆分文件,写入小文件大小....." << endl;
for (int i = 0; i < m_fileNumber; i++){
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_percemtageprogress.DisplayProgress(progressValue);
}
}
};
int main()
{
FileSplitter f("/d", 6);
f.split();
system("pause");
return 0;
}
按这样做法,违背了依赖倒置原则,即高层(FileSplitter)依赖低层(PercentageProgress),而不是依赖抽象。而且一旦以后要加一个显示方式,比如加一个进度条形式,那么就要修改FileSplitter,这又违反了开闭原则。
3.示例2
有如下的一个应用场景,即同一个页面,开4个窗口,或者像右下角一样,数据一样,但是窗口的表现形式不一样。当我们改变其中一个窗口的某个值时,其他窗口必定要同步变。

#include <iostream>
#include <vector>
using namespace std;
class Observer
{
public:
virtual void updata(void) const = 0;
};
class widowObs1 :public Observer
{
public:
virtual void updata(void) const
{
cout << "窗口1更新" << endl;
}
};
class Subject
{
public:
//加入需联动对象
void attach(Observer* ob)
{
mem.push_back(ob);
}
//改变窗口值
void set_value(const int& _val)
{
value = _val;
notify();//通知所有的需联动的对象
}
//通知
void notify(void)
{
for (vector<Observer*>::iterator it = mem.begin(); it != mem.end(); it++)
(*it)->updata();
}
private:
vector<Observer*> mem;
int value;
};
int main()
{
Subject sub;
sub.attach(new widowObs1);
sub.set_value(5);
system("pause");
return 0;
}
4.GoF给出的模式定义

5.模式结构

注: ConcreteSubject是Subject的子类,它实现了在Subject中定义的抽象业务逻辑方法,还可以有自己的方法,如果无须扩展Subject,则ConcreteSubject可以省略,就像文章给出的两个示例一样,均无ConcreteSubject类。
6.要点总结
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目
标与观察者,从而使二者之间的依赖关系达致松耦合。 - 目标发送通知时,无需指定观察者(
多态机制),通知(可以携带通知信息作
为参数)会自动传播。 - 观察者自己决定是否需要订阅通知(
客户程序),目标对象对此一无所知。 - Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
更多推荐
所有评论(0)