一步一步改写Observer观察者模式

news2024/11/18 7:41:49

简单的概述:

Observer模式是建立一个一(Subject)对多(Observer)的依赖关系,并且做到当“一”变化的时候,依赖这个“一”的多也能够同步自动改变。

Observer的结构:

Subject相当于消息的通知者,他提供对于依赖于他的观察者们进行添加注册,删除注销的操作,并且提供了使得依赖于他的所有观察者同步消息的操作Notify。

Observer就是观察者,他提供一个Update操作,注意这里的 Observer 的 Update 操作并不在Observer 改变了Subject目标状态的时候就对自己进行更新,这个更新操作要延迟到 Subject 对象发出 Notify 通知所有 Observer 进行修改(Notify里调用Update)。

除了上面两个主类,我们还需要具体的通知者类,和具体的观察者类。具体的通知者根据具体要求有着自己的Notify通知的函数,同理不同的观察者对于响应通知之后有着自己不同的update更新方式。

所以就清楚了Notify和update要作为纯虚函数存在,Observer,Subject就是抽象类,需要派生各个具体的子类

明白了上面的结构,我们举一个实例

对于苦逼的上班族来说摸鱼再正常不过了,当老板不在的时候,员工可以偷偷干一些自己的事情,但是总不可能看一眼老板摸一下鱼,看一眼老板摸一下鱼....这样肯定不行嘛,那么就需要一个人去通知大家老板有没有来。通知消息的人把和自己关系好的添加到通知名单里面,如果老板来了,就通知一下他们,被通知到的人就去更改自己的状态。

如下图所示:

我们创建一个Observer主类,假如我们摸鱼只能看球或者看股票,那么就要有具体的看球子类和看股票子类,不同的子类干不同的事情

对于Subject主类,我们只有一个秘书子类去当做具体的消息通知者

Observer范例代码:

//观察者模式
class Subject;
//员工 观察者
class Observer {//基类 抽象类
protected:
	string _name;
	Subject* _sub;
public:
	Observer(const string& name, Subject* sub)
		:_name(name), _sub(sub) {
		cout << "Create Observer" << endl;
	}

	virtual ~Observer() {
		cout << "Destroy Observer" << endl;
	}

	virtual void update() = 0;//得到消息后 不同的更新方式
};

//不同状态的员工
//去炒股
class StockObserver :public Observer {
public:
	StockObserver(const string& name, Subject* sub)
		:Observer(name, sub) {
		cout << "创建看股票的观察者" << endl;
	}
	~StockObserver() {
		cout << "析构看股票的观察者" << endl;
	}
	void update();
};
//去看球
class SoccerObserver :public Observer {
public:
	SoccerObserver(const string& name, Subject* sub)
		:Observer(name, sub) {
		cout << "创建看足球的观察者" << endl;
	}
	~SoccerObserver() {
		cout << "析构看足球的观察者" << endl;
	}
	void update();
};

//消息通知者
class Subject {//抽象类
protected:
	list<Observer*>_obslist;//存放着观察者的信息
	bool _is_boss;//boss来了没有
	string _message;//boss来没来,秘书持有的消息
public:
	Subject() :_is_boss(false), _message("boss没来,干自己的事") {
		cout << "创建消息通知者" << endl;
	}
	~Subject() {
		removeObserver();
		cout << "析构消息通知者" << endl;
	}
	void SetChanged() {
		_is_boss = true;
		_message = "boss来了!";
	}

	void ClearChanged() {
		_is_boss = false;
		_message = "boss没来,干自己的事";
	}
	bool GetChanged()const {
		//获得老板的状态
		return _is_boss;
	}
	const string& GetMessage()const {
		return _message;
	}
public:
	void addObserver(Observer* obs)//添加观察者
	{
		_obslist.push_back(obs);
	}
	void removeObserver(Observer* obs)//移除观察者
	{
		auto it = find(_obslist.begin(), _obslist.end(), obs);
		if (it != _obslist.end())
		{
			_obslist.erase(it);
		}
		//_obslist.remove(obs);
	}
	void removeObserver()//清空所有观察者
	{
		//重载
		_obslist.clear();
	}
	int countObserver()const //观察者个数
	{
		return _obslist.size();
	}
	virtual void notify() = 0;//通知 纯虚函数

};
//秘书类 继承消息通知者
class Secretary :public Subject {
public:
	Secretary() {
		cout << "创建秘书" << endl;
	}
	~Secretary() {
		cout << "析构秘书" << endl;
	}
public:
	void notify() {
		for (auto& obs : _obslist) {
			obs->update();
		}
		ClearChanged();
	}
};
void StockObserver::update()
{
	cout << _name << "接收到到信息" << endl;
	if (_sub->GetChanged())
	{
		cout << _sub->GetMessage() << "停止炒股,认真工作" << endl;
	}
	else
	{
		cout << "boss 没有来!" << _sub->GetMessage() << endl;
	}
}
void SoccerObserver::update()
{
	cout << _name << "接收到到信息" << endl;
	if (_sub->GetChanged())
	{
		cout << _sub->GetMessage() << "停止看球,认真工作" << endl;
	}
	else
	{
		cout << "boss 没有来!" << _sub->GetMessage() << endl;
	}
}
int main()
{
	//秘书
	Subject* ms = new Secretary();
	//员工
	Observer* zs = new SoccerObserver("张三", ms);
	Observer* xm = new SoccerObserver("小明", ms);
	Observer* xh = new StockObserver("小红", ms);
	//秘书和员工建立了良好关系,秘书把他们加入自己的受信列表里
	ms->addObserver(zs);
	ms->addObserver(xm);
	ms->addObserver(xh);
	//秘书通知一下
	ms->SetChanged();
	ms->notify();
	
	//把小明移除了
	ms->removeObserver(xm);
    ms->SetChanged();
	ms->notify();
	
	return 0;
}

上面就是一个基本的observer的demo

说明:
1. 在 Observer 模式的实现中,Subject 维护一个 list 作为存储其所有观察者的容器。每当调用 Notify 操作就遍历 list中的 Observer 对象,并广播通知改变状态(调用Observer的Update操作)。
2. 运行示例程序,可以看到当原始数据 Subject 处于状态 “false” 时候,依赖于它的两个观察者都显示 “false”,当原始数据状态改变为 “true” 的时候,依赖于它的两个观察者也都改变为“true”。
3. 可以看到 Observer 与 Subject 两个基类互为耦合,但是这种耦合的双方都依赖于抽象,而不依赖于具体。各个派生子类互相独立

下面我们改写上面的demo

跑了程序我们会发现,没有析构这些员工,即使多加一些delete,会很麻烦的,再者再通知notify函数,它里面是需要obs对象的,有可能调用顺序不对了,程序就死了

	delete zs;
	delete xm;
	delete xh;

那么需要用智能指针去解决,怎么用是取决于具体场景的。

如果说员工没有加班,上下班不需要请示,那么秘书这个通知者就以weak_ptr去管理,因为有可能秘书下班了,员工还没有下班,员工的存活秘书管不着。

如果规定员工上下班就得归秘书管,秘书不下班,员工也走不了~~,这样的话秘书就需要用shared_ptr去控制员工的生存期

下面的话我们以第二种方式去改写一下;

class Subject;
//员工 观察者
class Observer {//基类 抽象类
protected:
	string _name;
	//Subject* _sub;
	weak_ptr<Subject>_sub;
public:
	Observer(const string& name, weak_ptr<Subject> sub)
		:_name(name), _sub(sub) {
		cout << "Create Observer" << endl;
	}

	virtual ~Observer() {
		cout << "Destroy Observer" << endl;
	}

	virtual void update() = 0;//得到消息后 不同的更新方式
};


//不同状态的员工
//去炒股
class StockObserver :public Observer {
public:
	StockObserver(const string& name, weak_ptr<Subject> sub)
		:Observer(name, sub) {
		cout << "创建看股票的观察者" << endl;
	}
	~StockObserver() {
		cout << "析构看股票的观察者" << endl;
	}
	void update();
};
//去看球
class SoccerObserver :public Observer {
public:
	SoccerObserver(const string& name, weak_ptr<Subject> sub)
		:Observer(name, sub) {
		cout << "创建看足球的观察者" << endl;
	}
	~SoccerObserver() {
		cout << "析构看足球的观察者" << endl;
	}
	void update();
};


//消息通知者
class Subject {//抽象类
protected:
	//list<Observer*>_obslist;//存放着观察者的信息
	list<shared_ptr<Observer>>_obslist;
	bool _is_boss;//boss来了没有
	string _message;//boss来没来,秘书持有的消息
public:
	Subject() :_is_boss(false), _message("boss没来,干自己的事") {
		cout << "创建消息通知者" << endl;
	}
	~Subject() {
		removeObserver();
		cout << "析构消息通知者" << endl;
	}
	void SetChanged() {
		_is_boss = true;
		_message = "boss来了!";
	}

	void ClearChanged() {
		_is_boss = false;
		_message = "boss没来,干自己的事";
	}
	bool GetChanged()const {
		//获得老板的状态
		return _is_boss;
	}
	const string& GetMessage()const {
		return _message;
	}
public:
	void addObserver(shared_ptr<Observer>obs)//添加观察者
	{
		_obslist.push_back(obs);
	}
	void removeObserver(shared_ptr<Observer> obs)//移除观察者
	{
		auto it = find(_obslist.begin(), _obslist.end(), obs);
		if (it != _obslist.end())
		{
			_obslist.erase(it);
		}
		//_obslist.remove(obs);
	}
	void removeObserver()//清空所有观察者
	{
		//重载
		_obslist.clear();
	}
	int countObserver()const //观察者个数
	{
		return _obslist.size();
	}
	virtual void notify() = 0;//通知 纯虚函数

};
//秘书类 继承消息通知者
class Secretary :public Subject {
public:
	Secretary() {
		cout << "创建秘书" << endl;
	}
	~Secretary() {
		cout << "析构秘书" << endl;
	}
public:
	void notify() {
		for (auto& obs : _obslist) {
			obs->update();
		}
		ClearChanged();
	}
};
void StockObserver::update()
{
	cout << _name << "接收到到信息" << endl;
	shared_ptr<Subject>pa = _sub.lock();
	if (!pa)return;
	if (pa->GetChanged())
	{
		cout << pa->GetMessage() << "停止炒股,认真工作" << endl;
	}
	else
	{
		cout << "boss 没有来!" << pa->GetMessage() << endl;
	}
}
void SoccerObserver::update()
{
	cout << _name << "接收到到信息" << endl;
	shared_ptr<Subject>pa = _sub.lock();
	if (!pa)return;
	if (pa->GetChanged())
	{
		cout << pa->GetMessage() << "停止看球,认真工作" << endl;
	}
	else
	{
		cout << "boss 没有来!" << pa->GetMessage() << endl;
	}
}
int main()
{
	//秘书
	shared_ptr<Subject> ms(new Secretary());
	//员工
	shared_ptr<Observer> zs (new SoccerObserver("张三", ms));
	shared_ptr<Observer> xm(new SoccerObserver("小明", ms)) ;
	shared_ptr<Observer> xh(new StockObserver("小红", ms)) ;
	//秘书和员工建立了良好关系,秘书把他们加入自己的受信列表里
	ms->addObserver(zs);
	ms->addObserver(xm);
	ms->addObserver(xh);
	//秘书通知一下
	ms->SetChanged();
	ms->notify();
	
	//把小明移除了
	ms->removeObserver(xm);
	ms->SetChanged();
	ms->notify();
	
	return 0;
}

改写成多线程版本的Observer模式

依旧写的是消息发送者生存期到了员工才去析构,把上面demo版增改成如下:

void fun_xm(shared_ptr<Subject>sub)
{
	shared_ptr<Observer> xm(new SoccerObserver("小明", sub));
	sub->addObserver(xm);//此时秘书没死 xm也没死
	cout << "小明上班了."<<endl;
	for (int i = 0; i < 5; ++i)
	{
		this_thread::sleep_for(chrono::microseconds(1000));
	}
	cout << "小明下班了...." << endl;
}
void fun_zs(shared_ptr<Subject>sub)
{
	shared_ptr<Observer> zx(new SoccerObserver("张三", sub));
	sub->addObserver(zx);//此时秘书没死 zs也没死
	cout << "张三上班了." << endl;
}
void fun_xh(shared_ptr<Subject>sub)
{
	shared_ptr<Observer> xh(new StockObserver("小红", sub));
	sub->addObserver(xh);//此时秘书没死 xh也没死
	cout << "小红上班了." << endl;
}
int main()
{
	//秘书
	shared_ptr<Subject> sub(new Secretary());
	
	thread tha(fun_zs, sub);
	thread thb(fun_xm, sub);
	thread thc(fun_xh, sub);
	this_thread::sleep_for(chrono::microseconds(200));
	for (int i = 0; i < 5; ++i)
	{
		if (rand() % 2 == 0)
		{
			sub->SetChanged();
		}
		else
		{
			sub->ClearChanged();
		}
		sub->notify();
		cout << endl;
		this_thread::sleep_for(chrono::microseconds(500));
	}
	tha.join();
	thb.join();
	thc.join();
	return 0;
}


想只有一个消息的发送者的话,秘书这个类改写成单例模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/128619.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

H5 拖动排序 美食排行榜

尝试写一下拖动元素进行排序&#xff0c;真是想到什么去写什么 &#x1f602;&#xff0c;有的时候很多人老是跟我说&#xff0c;别人都封装好了&#xff0c;你为什么还要自己去实现一下&#xff0c;写的还没别人好。但我总感觉所有都用别人写好的&#xff0c;就放弃思考的机会…

linux的CPU使用率达到100%的快速定位方式

一.问题现象 Linux服务器&#xff08;操作系统版本是centos7.9&#xff09;上面部署了若干Java站点服务,突然收到运维的CPU异常g告警&#xff0c;到了影响业务的情况发生&#xff0c;经初步排查&#xff0c;未出现异常进程&#xff0c;排除挖矿病毒的原因。 二.排查思路 &am…

[Verilog]Verilog经典电路设计(一)

Verilog经典电路设计&#xff08;一&#xff09; 1.1 8位移位寄存器 module shifter (din , clk, clr, dout) ; input din, clk, clr; output [7:0] dout; reg [7:0] dout_data;always (posedge clk) begin if (!clr) dout_data < 8b0; //同步清 &#xff0c;高电平…

第二证券|美国巨头向欧盟宣战,暴跌25%,芯片大国告急

“暴利税”完全激怒美国动力巨子。 3万亿美国巨子直接将欧盟告上法庭&#xff0c;当地时间12月28日&#xff0c;埃克森美孚公司正式申述欧盟&#xff0c;要求其撤销对石油集团征收的一项新“暴利税”。该公司表明&#xff0c;欧盟方面征收“暴利税”的行为超出了法律权限。别的…

蓝牙学习八(配对与绑定)

1.简介 Paring&#xff08;配对&#xff09;和Bonding&#xff08;绑定&#xff09;是实现蓝牙射频通信安全的一种机制&#xff0c;有两点需要注意&#xff1a; Paring/bonding实现的是蓝牙链路层的安全&#xff0c;对应用层来说是完全透明的。也就是说&#xff0c;不管有没有…

GitHub入门指南(下)

三、新手必备的GitHub基本操作 1.配置SSH Key (1) 第一次使用时&#xff0c;要配置一下账户。 在 Git Bash 客户端&#xff0c;输入&#xff1a; git config --global user.name “这里输入你在GitHub的账户名” git config --global user.email “这里输入你在GitHub的注册邮…

基于51单片机的数字电压表(PCF8591)(Proteus仿真+程序)

编号&#xff1a;32 基于51单片机的数字电压表&#xff08;PCF8591&#xff09; 功能描述&#xff1a; 本设计由51单片机最小系统PCF8591模块四路模拟量输入模块一路DA输出液晶1602显示模块 1、主控制器是89C82单片机。 2、PCF8591模数转换器进行A/D转换&#xff0c;读取四路…

Java中常见的文件操作

作者&#xff1a;~小明学编程 文章专栏&#xff1a;JavaEE 格言&#xff1a;热爱编程的&#xff0c;终将被编程所厚爱。 目录 操作文件 File类 属性 构造方法 常见方法 重要方法的操作演示 文件内容的读写 FileInputStream OutputStream 按照字符读入 按照字符写入…

『分分钟玩转VueRouter●中』少开一把王者荣耀掌握VueRouter的基本使用

文章目录一、编程式路由导航二、缓存路由组件三、两个新的声明周期钩子四、路由守卫五、路由器的两种工作模式本篇博客会介绍Vue中的VueRouter的基本使用&#xff0c;编程式路由导航增加了我们进行路由跳转的灵活性&#xff0c;缓存路由组件保障了我们使用路由时的便捷性&#…

【高阶数据结构】搜索二叉树 经典习题讲解

&#x1f308;欢迎来到数据结构专栏~~搜索二叉树 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句…

【OpenFOAM】-olaFlow-算例6- waveFloatingObject

算例路径&#xff1a; olaFlow\tutorials\waveFloatingObject 算例描述&#xff1a; 波浪作用下的浮体的刚体运动&#xff0c;属于流固耦合&#xff08;FSI&#xff09;问题 学习目标&#xff1a; 动网格设置和使用&#xff0c;网格变形控制&#xff0c;浮体的物理参数设置&…

23种设计模式(二)——享元模式【对象性能】

文章目录意图什么时候使用享元享元模式的实现内部状态和外部状态享元模式的优缺点与其他模式的关系亦称&#xff1a; 缓存、Cache、Flyweight 意图 享元模式是一种结构型设计模式&#xff0c; 它摒弃了在每个对象中保存所有数据的方式&#xff0c; 通过共享多个对象所共有的相…

数图互通房产管理系统架构分析

数图互通高校房产管理系统V5.0 使用JAVA、Canvas、H5等技术开发的图形数据交互技术架构平台&#xff1b;本系统满足XX大学房屋管理系统需求&#xff0c;高校房产综合管理信息系统平台V5.0遵循高校房产“分级授权、分类管理、网络化、图形化、精细化、流程化”的管理理念&#x…

关于新冠的几点总结

关于新冠的几点总结一、前言:二、病程阶段1. 第一阶段 反复发热2. 第二极端 退烧虚弱3. 第三阶段 咳嗽嗜睡三、处置措施:1. 思想准备2. 药/物准备3. 退烧方式4. 保持体温5. 通则不痛&#xff0c;痛则不通6. 营养补充7. 恢复关键期写在最后一、前言: 所写内容&#xff0c;为个人…

磊科路由器后门蜜罐捕获的事件分布情况

重点物联网 漏洞利用情况本节我们选取了两个漏洞进行分析。UPnP 相关的漏洞我们将在 4.4.3 进行分析&#xff0c;除去 UPnP 相关漏 洞外&#xff0c;被利用最多的是 Eir D1000 路由器的一个漏洞 [44]&#xff08;CVE-2016-10372&#xff09;&#xff0c;我们将对其进行分析。 …

Apollo浅解2

目录 用户、角色、权限 三者间的关系 权限Permission 新增一个应用时 新增一个命名空间时 角色Role 新增一个应用时 新增一个命名空间时 第三方应用 用户、角色、权限 三者间的关系 apollo也采用经典的三层权限设计&#xff0c;用户关联角色&#xff0c;角色关联权限…

DOM基础

一、DOM的概念 文档对象模型(DOM,Document Object Module)是W3C组织推荐的处理可扩展标志语言的标准编程接口&#xff0c;它允许程序和脚本动态的访问和更新文档的内容、结构和样式。 HTML的DOM操作是将文档里所有的内容(包括标签、标签里的内容、标签属性甚至注释等)都当做一…

51单片机入门 第一篇:LED灯

文章目录前言一、LED原理图二、创建keil5工程三、代码的编写四、程序的烧录总结前言 本篇文章讲正式带大家开始学习51单片机&#xff0c;希望这些文章能够很好的帮助到大家学习51单片机。 一、LED原理图 一般的51单片机上都带有8个LED灯&#xff0c;这里8个LED灯分别接到了板…

JS逆向——工信部ICP、IP、域名信息备案管理平台

问题&#xff1a;&#xff08;1&#xff09;数据列表接口token参数验证&#xff08;2&#xff09;authKey参数加密生成 1、页面中请求接口&#xff0c;观察请求头可发现&#xff0c;校验参数token为加密的字符串&#xff0c;根据该字符串并不能直观得到所用的加密方式是什么。 …

数据库大小写不敏感后,值也不敏感了

现象&#xff1a;我有一个账号admin&#xff0c;结果莫名多了一个ADMIN、Admin、AdMin等一些列账号&#xff1b;细品你的密码就算密文签名&#xff0c;是不是在你不知情的情况下也有很多。 原因&#xff1a;数据库安装的时候设置的大小写不敏感导致 解决&#xff1a;建议第三…