C++设计模式03-——观察者设计模式
文章目录
一、观察者模式的定义
观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
二、快速理解观察者设计模式
1. 概述理解
何为观察者?
我们说的观察者,是一件事情的围观者。对于这件事情的主人翁,制造这件事情的发布者;对于围观者来说,他们就是观察者或者称为订阅者。
在设计模式中,外界的变化是造成耦合的重要原因,有时候为了降低耦合,就必须抵御变化。将事物源(目标者)与收听者(观察者)隔离开来便有了观察者模式。
抵御变化不是消除变化,假设变化是一只猫,活动范围是一间屋子,而设计模式能做的是把这只猫关进一个笼子里面。
——— 李建忠
观察者模式"要素察觉":
- 一种一对多的依赖关系
- 发布者(稳定) === INFO ===> 接收者(变化)
- 第2.中发布者发布的INFO是相同的,但是接收者拿到INFO后的行为各不相同。
2. 问题场景
比如:某个学校实验室与一个教授合作,实验室把课题通知给教授,在这个场景下,目标者就是实验室,收听者就算该教授,他们展开了愉快的合作:
- 教授:professor.h
#pragma once
#include <iostream>
using namespace std;
class Professor {
public:
Professor() {}
void setTask(string task) {
cout << "分配到的课题为:" << task << ' ';
cout << "打算与业内前沿研究专家学者讨论..." << endl;
}
};
- 实验室:lab.h
#pragma once
#include <iostream>
#include <string>
#include "professor.h"
class Lab {
public:
Lab(Professor* prof, string task)
: professor(prof),
researchTask(task)
{}
void inform() {
if (professor != nullptr) {
professor->setTask(researchTask);
}
}
private:
Professor* professor;
string researchTask;
};
- 开启工作:
#include "professor.h"
#include "lib.h"
#include <iostream>
using namespace std;
int main() {
Professor* prof = new Professor();
Lab* lab = new Lab(prof, "机器学习");
lab->inform();// 告诉这个教授,研究的课题是"机器学习"
return 0;
}
- 输出结果
分配到的课题为:机器学习 打算与业内前沿研究专家学者讨论…
但是,突然有个在读博士生对这个研究课题很感兴趣,他们要加入这个实验室,然后,根据最朴素的思想:
- 博士生类: doctor.h
#pragma once
#include <iostream>
using namespace std;
class Doctor {
public:
Doctor() {}
void setTask(string task) {
cout << "分配到的课题为:" << task << ' ';
cout << "打算查资料做实验思考..." << endl;
}
};
为了接入这位博士,实验室被迫增加与这个博士的联系
实验室改动!
2. 实验室更新:lab.h
#pragma once
#include <iostream>
#include <string>
#include "professor.h"
#include "doctor.h"
using namespace std;
class Lab {
public:
Lab(Professor* prof, Doctor* doc, string task)
: proffessor(prof),
doctor(doc),
researchTask(task)
{}
void inform() {
if (professor != nullptr) {
professor->setTask(researchTask);
}
if (doctor != nullptr) {
doctor->setTask(researchTask);
}
}
private:
Professor* professor;
Doctor* doctor;
string researchTask;
};
- 开启工作:
#include "professor.h"
#include "lib.h"
#include "doctor.h"
#include <iostream>
using namespace std;
int main() {
Professor* prof = new Professor();
Doctor* doc = new Doctor();
Lab* lab = new Lab(prof, doc, "机器学习");
lab->inform();// 告诉这个教授和博士,研究的课题是"机器学习"
return 0;
}
- 输出结果:
分配到的课题为:机器学习 打算与业内前沿研究专家学者讨论…
分配到的课题为:机器学习 打算查资料做实验思考…
我们发现,随着不同身份的人加入实验室,我们的实验室就要被迫与这些人建立联系,在代码上面的体现就是每增加一个成员都需要修改代码,此时有两种情况:
- 如果增加人员的身份已经在实验室出现过:说明可能这一种身份的人不只一个,为了解决这个问题,只需要:把一类人放入vector< identity>中即可
- 如果没有出现过,根据1.,需要新造一个vector放在实验室类中的私有成员域中即可,如2个教授,2个博士:
改进的实验室类:lab.h
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include "professor.h"
#include "doctor.h"
using namespace std;
class Lab {
public:
Lab(Professor* prof[], Doctor* doc[], string task)
: professor(prof),
doctor(doc),
researchTask(task)
{}
Lab* addProfessor(Professor *prof) {
professors.push_back(prof);
return this;
}
Lab* addDoctor(Doctor *doc) {
doctors.push_back(doc);
return this;
}
void inform() {
for (auto& professor : professors) {
if (professor != nullptr) {
professor->setTask(researchTask);
}
}
for (auto& doctor : doctors) {
if (doctor != nullptr) {
doctor->setTask(researchTask);
}
}
}
private:
vector<Professor *> professors;
vector<Doctor *> doctors;
string researchTask;
};
调用:
#include "professor.h"
#include "lib.h"
#include "doctor.h"
#include <iostream>
using namespace std;
using namespace std;
int main() {
Professor* p1 = new Professor();
Professor* p2 = new Professor();
Doctor* d1 = new Doctor();
Doctor* d2 = new Doctor();
Lab* lab = new Lab("机器学习");
lab->addProfessor(p1)->addProfessor(p2)->addDoctor(d1)->addDoctor(d2);
lab->inform(); // 告诉教授们和博士们课题
return 0;
}
但是,这样做,也不可避免地修改实验室类中的代码,这样,对于每类参与实验室研究任务的人,都需要实验室更改代码。
3. 观察者模式
根据上面的引子,我们看到前面有几个注释:
- lab->inform(); // 告诉这个教授,研究的课题是"机器学习"
- lab->inform(); // 告诉这个教授和学生,研究的课题是"机器学习"
- lab->inform(); // 告诉教授们和博士们课题
这就是问题的来源!
我们发现重复的地方了,我们其实并不关心是教授还是博士生,我们不关心他们拿到课题后如何研究,只关心把任务交给他们,这些任务格式都是相同的。
也就是说,他们只有一个身份,就是信息的接收者,而实验室就是信息源。于是我们想到上面:
vector<Professor *> professors;
vector<Doctor *> doctors;
为何不用:
vector<Researcher *> researchers;
于是,我们不妨向上抽象一层,想象Professor和Doctor均是Researcher的子类,而且不光是这样,接下来无论是什么身份,都以Researcher为基类。这个Reseracher就是观察者。
于是:
- 研究人员抽象类:researcher.h
#pragma once
#include <iostream>
#include <string>
using namespace std;
class Researcher {
public:
Researcher() {}
virtual void setTask(string task) = 0;
};
- 教授研究者类:
#pragma once
#include <string>
#include "researcher.h"
using namespace std;
class Professor : public Researcher {
public:
Professor() {}
virtual void setTask(string task) {
cout << "分配到的课题为:" << task << ' ';
cout << "打算与业内前沿研究专家学者讨论..." << endl;
}
};
- 博士生研究者类:
#pragma once
#include <string>
#include "researcher.h"
using namespace std;
class Doctor : public Researcher {
public:
Doctor() {}
virtual void setTask(string task) {
cout << "分配到的课题为:" << task << ' ';
cout << "打算查资料做实验思考..." << endl;
}
};
- 实验室类:
#pragma once
#include "doctor.h"
#include "professor.h"
#include <vector>
using namespace std;
class Lab {
public:
Lab(string task) : researchTask(task) {}
Lab* addResearcher(Researcher* res) {
researchers.push_back(res);
return this;
}
void inform() {
for (auto& researcher : researchers) {
if (researcher != nullptr) {
researcher->setTask(researchTask);
}
}
}
private:
vector<Researcher *> researchers;
string researchTask;
};
- 开启工作:
#include <iostream>
#include "lab.h"
using namespace std;
int main() {
Professor* p1 = new Professor();
Professor* p2 = new Professor();
Doctor* d1 = new Doctor();
Doctor* d2 = new Doctor();
Lab* lab = new Lab("机器学习");
lab->addResearcher(p1)->addResearcher(p2)->addResearcher(d1)->addResearcher(d2);
lab->inform();
return 0;
}
- 输出结果:
分配到的课题为:机器学习 打算与业内前沿研究专家学者讨论…
分配到的课题为:机器学习 打算与业内前沿研究专家学者讨论…
分配到的课题为:机器学习 打算查资料做实验思考…
分配到的课题为:机器学习 打算查资料做实验思考…
这样他们每个人都拿到了实验课题,并且都有了自己的初步解决方案。那么这个 信息源 ===> 观察者就是成功的。接下来无论有多少观察者都无所谓,当然,为了完整展示,这个模式较为完整的结构:(目标对象中的方法)
addObserver(); // 添加观察者, 将观察者放置容器中
removeObserver(params…); // 移除观察者,将观察者从容器中移除
informObserver(); // 遍历容器中的观察者,调用其统一的、继承纯虚基类的方法
4. 模式小结
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
- 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
- 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
- Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分
三、观察者设计模式案例
1. 文件分割器进度显示(极客班)
初始:
#pragma once
#include <iostream>
#include <string>
using namespace std;
class ProgressBar {
public:
ProgressBar() {}
~ProgressBar() {}
void setValue(float value) {
cout << "进度条设置值为: " << value << endl;
}
};
class FileSplitter
{
string m_filePath;
int m_fileNumber;
ProgressBar* m_progressBar;
public:
FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
m_filePath(filePath),
m_fileNumber(fileNumber),
m_progressBar(progressBar) {
}
void split() {
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++) {
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_progressBar->setValue(progressValue);
}
}
};
改进:
#pragma once
#include <string>
#include <list>
using namespace std;
class IProgress {
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress() {}
};
class FileSplitter
{
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.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++) {
//...
float progressValue = (float)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)->DoProgress(value); //更新进度条
itor++;
}
};
根据依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
初始时,文件分割器进度显示是一个具体的显示方法,耦合程度高,它的形式既可以使用使用点来表示进度,也可以用进度条,也可以用加载动态图片这些具体方法来显示进度,一旦显示的形式发生变化,文件分割器也会因为这个辅件受影响而需要修改。
经过修改,抽象通知机制,支持通知多个观察者,可以支持多种形式,灵活方便。
2. 天气情况预报实时(尚硅谷)
点击链接,尚硅谷-设计模式-观察者模式,韩老师以java为例讲解设计模式,讲得很清楚,推荐!
THE END