C++设计模式03-——观察者设计模式

C++设计模式03-——观察者设计模式

一、观察者模式的定义

  观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
 

二、快速理解观察者设计模式

1. 概述理解

何为观察者?
  我们说的观察者,是一件事情的围观者。对于这件事情的主人翁,制造这件事情的发布者;对于围观者来说,他们就是观察者或者称为订阅者。
  在设计模式中,外界的变化是造成耦合的重要原因,有时候为了降低耦合,就必须抵御变化。将事物源(目标者)与收听者(观察者)隔离开来便有了观察者模式。

   抵御变化不是消除变化,假设变化是一只猫,活动范围是一间屋子,而设计模式能做的是把这只猫关进一个笼子里面。

——— 李建忠

  观察者模式"要素察觉":

  1. 一种一对多的依赖关系
  2. 发布者(稳定) === INFO ===> 接收者(变化)
  3. 第2.中发布者发布的INFO是相同的,但是接收者拿到INFO后的行为各不相同。
2. 问题场景

  比如:某个学校实验室与一个教授合作,实验室把课题通知给教授,在这个场景下,目标者就是实验室,收听者就算该教授,他们展开了愉快的合作:

  1. 教授:professor.h
#pragma once
#include <iostream>

using namespace std;

class Professor {
public:
	Professor() {}
	void setTask(string task) {
		cout << "分配到的课题为:" << task << ' ';
		cout << "打算与业内前沿研究专家学者讨论..." << endl;
	}
};
  1. 实验室: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;
};
  1. 开启工作:
#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;
}
  1. 输出结果

分配到的课题为:机器学习 打算与业内前沿研究专家学者讨论…

但是,突然有个在读博士生对这个研究课题很感兴趣,他们要加入这个实验室,然后,根据最朴素的思想:

  1. 博士生类: 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;
};
  1. 开启工作:
#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;
}
  1. 输出结果:

分配到的课题为:机器学习 打算与业内前沿研究专家学者讨论…
分配到的课题为:机器学习 打算查资料做实验思考…


我们发现,随着不同身份的人加入实验室,我们的实验室就要被迫与这些人建立联系,在代码上面的体现就是每增加一个成员都需要修改代码,此时有两种情况:

  1. 如果增加人员的身份已经在实验室出现过:说明可能这一种身份的人不只一个,为了解决这个问题,只需要:把一类人放入vector< identity>中即可
  2. 如果没有出现过,根据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. 观察者模式

根据上面的引子,我们看到前面有几个注释:

  1. lab->inform(); // 告诉这个教授,研究的课题是"机器学习"
  2. lab->inform(); // 告诉这个教授和学生,研究的课题是"机器学习"
  3. lab->inform(); // 告诉教授们和博士们课题
    这就是问题的来源!

   我们发现重复的地方了,我们其实并不关心是教授还是博士生,我们不关心他们拿到课题后如何研究,只关心把任务交给他们,这些任务格式都是相同的。
   也就是说,他们只有一个身份,就是信息的接收者,而实验室就是信息源。于是我们想到上面:

vector<Professor *> professors;
vector<Doctor *> doctors;

为何不用:

vector<Researcher *> researchers;

   于是,我们不妨向上抽象一层,想象Professor和Doctor均是Researcher的子类,而且不光是这样,接下来无论是什么身份,都以Researcher为基类。这个Reseracher就是观察者。
于是:

  1. 研究人员抽象类:researcher.h
#pragma once
#include <iostream>
#include <string>

using namespace std;

class Researcher {
public:
	Researcher() {}
	virtual void setTask(string task) = 0;
};

  1. 教授研究者类:
#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;
    }
};
  1. 博士生研究者类:
#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;
    }
};
  1. 实验室类:
#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;
};
  1. 开启工作:
#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;
}
  1. 输出结果:

分配到的课题为:机器学习 打算与业内前沿研究专家学者讨论…
分配到的课题为:机器学习 打算与业内前沿研究专家学者讨论…
分配到的课题为:机器学习 打算查资料做实验思考…
分配到的课题为:机器学习 打算查资料做实验思考…

这样他们每个人都拿到了实验课题,并且都有了自己的初步解决方案。那么这个 信息源 ===> 观察者就是成功的。接下来无论有多少观察者都无所谓,当然,为了完整展示,这个模式较为完整的结构:(目标对象中的方法)

addObserver(); // 添加观察者, 将观察者放置容器中
removeObserver(params…); // 移除观察者,将观察者从容器中移除
informObserver(); // 遍历容器中的观察者,调用其统一的、继承纯虚基类的方法

4. 模式小结
  1. 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
  2. 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
  3. 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
  4. 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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值