设计模式:观察者模式(Observer)

设计模式:观察者模式(Observer)

  • 设计模式:观察者模式(Observer)
    • 模式动机
    • 模式定义
    • 模式结构
    • 时序图
    • 模式实现
    • 观察者模式在单线程环境下的测试
    • 观察者模式在多线程环境下的测试
    • 多线程下的观察者模式
    • 模式分析
    • 优缺点
    • 适用场景
    • 应用场景
    • 模式拓展:MVC 模式
    • 参考

设计模式:观察者模式(Observer)

观察者模式(Observer Pattern)属于行为型模式(Behavioral Pattern)的一种。

行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。

行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。

通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。

行为型模式分为类行为型模式和对象行为型模式两种:

  1. 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
  2. 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。

模式动机

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。

模式定义

观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。

观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

模式结构

  1. 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
  2. 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
  3. 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
  4. 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。

在这里插入图片描述

观察者模式通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。

时序图

在这里插入图片描述

模式实现

被观察者基类 Subject.h:

#ifndef _SUBJECT_H_
#define _SUBJECT_H_

#include "Observer.h"
#include <list>
/**
 * 被观察者基类
 * 1. 观察者需要在这里注册、注销
 * 2. 当被观察者发生变化时,需要通知观察者
*/
class Subject
{
protected:
	std::list<Observer*> m_ObserverList;

public:
	Subject() {}
	virtual ~Subject() = default;
	
	virtual void attach(Observer*) = 0; // 向该目标绑定一个观察者
	virtual void detach(Observer*) = 0; // 将观察者从该目标上注销
	virtual void notify() = 0; // 目标自身发生变化时,通知所有的观察者
	virtual int getState() = 0; // 取得目标的值
	virtual void setState(int) = 0; // 修改目标的值
};

#endif // !_SUBJECT_H_

被观察者派生类 ConcreteSubject.h:

#ifndef _CONSRETE_SUBJECT_H_
#define _CONSRETE_SUBJECT_H_

#include "Observer.h"
#include "Subject.h"

class ConcreteSubject : public Subject
{
private:
	int state;

public:
	ConcreteSubject() {}
	virtual ~ConcreteSubject() {}

	void attach(Observer* pObserver)
	{
		m_ObserverList.push_back(pObserver);
	}
	void detach(Observer* pObserver)
	{
		m_ObserverList.remove(pObserver);
	}
	void notify()
	{
		std::list<Observer*>::iterator iter = m_ObserverList.begin();
		while (iter != m_ObserverList.end())
		{
			(*iter)->update(this);
			iter++;
		}
	}

	int getState()
	{
		return state;
	}

	void setState(int st)
	{
		state = st;
	}
};

#endif // !_CONSRETE_SUBJECT_H_

观察者基类 Observer.h:

#ifndef _OBSERVER_H_
#define _OBSERVER_H_

class Subject;
// 观察者基类
class Observer
{
public:
	Observer() {}
	virtual ~Observer() = default;

	virtual void update(Subject*) = 0; // 接收从目标传递过来的内容,并进行处理
};

#endif // !_OBSERVER_H_

观察者派生类 ConcreteObserver.h:

#ifndef _CONSRETE_OBSERVER_H_
#define _CONSRETE_OBSERVER_H_

#include "Observer.h"
#include "Subject.h"
#include <iostream>

class ConcreteObserver : public Observer
{
private:
	int observerState;

public:
	ConcreteObserver() {}
	virtual ~ConcreteObserver() {}

	void update(Subject* pSubject)
	{
		observerState = pSubject->getState();
		std::cout << "ConcreteObserver get the update New State: " << observerState << std::endl;
	}
};

#endif // !_CONSRETE_OBSERVER_H_

观察者模式在单线程环境下的测试

测试代码 main.cpp:

#include "ConcreteSubject.h"
#include "ConcreteObserver.h"
using namespace std;

int main()
{
	ConcreteSubject* pSubject = new ConcreteSubject();
	Observer* pObserver1 = new ConcreteObserver();
	Observer* pObserver2 = new ConcreteObserver();

	pSubject->attach(pObserver1);
	pSubject->attach(pObserver2);
	pSubject->setState(2);
	pSubject->notify();
	cout << "-------------------------" << endl;
	pSubject->detach(pObserver1);
	pSubject->setState(3);
	pSubject->notify();
	
	delete pObserver1;
	delete pObserver2;
	delete pSubject;

	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

本程序创建了一个目标 pSubject,两个观察者 pObserver1 和 pObserver2,将这两个观察者注册在目标上。

设置目标的 state 为 2,然后使用 notify() 函数进行通知。可以看到,两个观察者都对目标的变化做出来响应;

目标注销 pObserver1,再次设置目标的 state 为 3,然后使用 notify() 函数进行通知。可以看到,现在只有一个观察者对目标的变化做出来响应。

观察者模式在多线程环境下的测试

测试代码 main.cpp:

#include "ConcreteSubject.h"
#include "ConcreteObserver.h"
#include <thread>
using namespace std;

ConcreteSubject* pSubject = new ConcreteSubject();
Observer* pObserver = new ConcreteObserver();

static void func1()
{
	pSubject->setState(1);
	pSubject->notify();
}

static void func2()
{
	pSubject->setState(2);
	pSubject->notify();
}

int main()
{
	pSubject->attach(pObserver);

	thread t1(func1);
	thread t2(func1);
	t1.join();
	t2.join();

	pSubject->detach(pObserver);
	
	delete pObserver;
	delete pSubject;

	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

之前的代码显然不能用于多线程,不能让多线程操作一个没有同步的数据结构。

多线程下的观察者模式

只需要修改被观察者的派生类的 attach、detach、notify 三个函数,加上互斥锁。

mutex类提供了能用于保护共享数据免受从多个线程同时访问的同步原语。

创建lock_guard对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。

修改后的 ConcreteSubject.h:

#ifndef _CONSRETE_SUBJECT_H_
#define _CONSRETE_SUBJECT_H_

#include "Observer.h"
#include "Subject.h"
#include <mutex>

class ConcreteSubject : public Subject
{
private:
	int state;
	std::mutex _mutex;

public:
	ConcreteSubject() {}
	virtual ~ConcreteSubject() {}

	void attach(Observer* pObserver)
	{
		std::lock_guard<std::mutex> guard(_mutex);
		m_ObserverList.push_back(pObserver);
	}
	void detach(Observer* pObserver)
	{
		std::lock_guard<std::mutex> guard(_mutex);
		m_ObserverList.remove(pObserver);
	}
	void notify()
	{
		std::lock_guard<std::mutex> guard(_mutex);
		std::list<Observer*>::iterator iter = m_ObserverList.begin();
		while (iter != m_ObserverList.end())
		{
			(*iter)->update(this);
			iter++;
		}
	}

	int getState()
	{
		return state;
	}

	void setState(int st)
	{
		state = st;
	}
};

#endif // !_CONSRETE_SUBJECT_H_

多线程下的测试代码不变,以下是测试结果:

在这里插入图片描述

模式分析

  • 观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统。
  • 这一模式中的关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知。
  • 作为对这个通知的响应,每个观察者都将即时更新自己的状态,以与目标状态同步,这种交互也称为发布-订阅(publish-subscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通

优缺点

优点:

  1. 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
  2. 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
  3. 观察者模式支持广播通信。
  4. 观察者模式符合“开闭原则”的要求。

缺点:

  1. 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

适用场景

在以下情况下可以使用观察者模式:

  1. 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  2. 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  3. 一个对象必须通知其他对象,而并不知道这些对象是谁。
  4. 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

应用场景

观察者模式在软件开发中应用非常广泛,如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息,某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。

模式拓展:MVC 模式

MVC模式是一种架构模式,它包含三个角色:模型(Model),视图(View)和控制器(Controller)。观察者模式可以用来实现MVC模式,观察者模式中的观察目标就是MVC模式中的模型(Model),而观察者就是MVC中的视图(View),控制器(Controller)充当两者之间的中介者(Mediator)。当模型层的数据发生改变时,视图层将自动改变其显示内容。

参考

  1. https://design-patterns.readthedocs.io/zh-cn/latest/behavioral_patterns/observer.html
  2. https://blog.csdn.net/sinat_38816924/article/details/122760490
  3. https://www.jianshu.com/p/a14ccd51b97d
  4. https://www.runoob.com/design-pattern/observer-pattern.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机不联网是否有IP地址

计算机不联网是否会有IP地址&#xff0c;这个问题涉及到计算机网络的基础知识。要深入探讨这个问题&#xff0c;我们需要从IP地址的定义、作用&#xff0c;以及计算机在不联网状态下的网络配置等多个方面进行分析。 首先&#xff0c;IP地址&#xff08;Internet Protocol Addre…

HCIA--综合实验(超详细)

要求&#xff1a; 1. 使用172.16.0.0/16划分网络 2.使用ospf协议合理规划区域保证更新安全 3.加快收敛速度 4. r1为DR没有BDR 5.PC2&#xff0c;3&#xff0c;4&#xff0c;5自动获取IP地址&#xff1b;PC1为外网&#xff0c;PC要求可用互相访问 6.r7为运营商&#xff0c;只能配…

Oracle 正则,开窗,行列转换

1.开窗函数 基本上在查询结果上添加窗口列 1.1 聚合函数开窗 基本格式: ..... 函数() over([partition by 分组列,...][order by 排序列 desc|asc][定位框架]) 1&#xff0c;partition by 字段 相当于group by 字段 起到分组作用2&#xff0c;order by 字段 即根据某个字段…

Java实现优先级队列(堆)

前言 在学习完二叉树的相关知识后&#xff0c;我们对数据结构有了更多的认识&#xff0c;本文将介绍到优先级队列(堆&#xff09; 1.优先级队列 1.1概念 前面介绍过队列&#xff0c;队列是一种先进先出(FIFO)的数据结构&#xff0c;但有些情况下&#xff0c;操作的数据可能…

万兆以太网MAC设计(1)10G PCS PMA IP核使用

文章目录 一、设计框图二、模块设计三、IP核配置四、上板验证五、总结 一、设计框图 关于GT高速接口的设计一贯作风&#xff0c;万兆以太网同样如此&#xff0c;只不过这里将复位逻辑和时钟逻辑放到了同一个文件ten_gig_eth_pcs_pma_0_shared_clock_and_reset当中。如果是从第…

将图片按灰度转换成字符

from struct import *ch [., :, !, ~, ,, ^, *,$, #] ch.reverse()def calc(R, G, B):#模式Lreturn R * 299 // 1000 G * 587 // 1000 B * 144 / 1000def character( val):num val / 260 * len(ch)num round(num)if num>len(ch):numlen(ch)-1return ch[num]class rmb:d…

浅谈Spring的Bean生命周期

在Spring框架中&#xff0c;Bean&#xff08;即Java对象&#xff09;的生命周期涵盖了从创建到销毁的全过程&#xff0c;主要包含以下几个阶段&#xff1a; 实例化&#xff08;Instantiation&#xff09;&#xff1a; 当Spring IoC容器需要创建一个Bean时&#xff0c;首先会通过…

【安全设备】Hfish如何测试

部署好了蜜罐如何测试&#xff1f; 1、查找节点 2、节点的端口 3、将部署的主机和节点联系在一起 http访问。 就可以测试了。

Win10系统VScode远程连接VirtualBox安装的Ubuntu20.04.5

1.打开虚拟机&#xff0c;在中端中输入命令: sudo apt-get install openssh-server 安装ssh 我这里已经安装完成&#xff0c;故显示是这样 2.输入命令&#xff1a;sudo systemctl start ssh 启动远程连接 注意&#xff0c;如果使用VirtualBox安装的虚拟机&#xff0c;需要启用…

Git分布式版本控制系统——在IDEA中使用Git(一)

一、在IDEA中配置Git 本质上还是使用的本地安装的Git软件&#xff0c;所以需要在IDEA中配置Git 打开IDEA的设置页面&#xff0c;按照下图操作 二、在IDEA中使用Git获取仓库 1、本地初始化仓库 2、从远程仓库克隆 方法一&#xff1a; 方法二&#xff1a; 三、.gitignore文件…

测绘管理与法律法规 | 测绘资质分类分级标准 | 学习笔记

目录 1. 申请条件 2.审批程序 3.专业技术人员的特殊规定 1. 申请条件 法人资格&#xff1a;申请单位必须具有法人资格。 专业技术人员&#xff1a;需拥有与测绘活动相适应的测绘专业技术人员和相关专业技术人员。 技术装备&#xff1a;具备与测绘活动相适应的技术装备和设…

js-利用blur使文本框自动控制格式

在 JavaScript 中&#xff0c;blur 是一个事件&#xff0c;它在一个元素失去焦点时触发。当用户从一个元素中移开或者将焦点转移到页面上的另一个元素时&#xff0c;该元素将触发 blur 事件。这个事件通常用于验证用户输入或执行其他与用户交互相关的操作。 假设我有个文本框&…

工业物联网让“制造”变成“智造”!——青创智通

工业物联网解决方案-工业IOT-青创智通 随着科技的不断进步和工业的持续发展&#xff0c;物联网&#xff08;IoT&#xff09;技术的出现为制造业带来了前所未有的变革。工业物联网&#xff08;IIoT&#xff09;作为物联网技术在工业领域的应用&#xff0c;正在逐渐改变传统的制…

Netty学习——高级篇2 Netty解码技术

接上篇&#xff1a;Netty学习——高级篇1 拆包 、粘包与编解码技术&#xff0c;本章继续介绍Netty的其他解码器 1 DelimiterBasedFrameDecoder分隔符解码器 DelimiterBasedFrameDecoder 分隔符解码器是按照指定分隔符进行解码的解码器&#xff0c;通过分隔符可以将二进制流拆分…

数据的插入、修改和删除

一、 插入数据 1. 向表中所有字段插入数据 &#xff08;1&#xff09; 指定所有字段及其相对应的值 insert into 表名(字段1&#xff0c;字段2&#xff0c;……) values(字段值1&#xff0c;字段值2&#xff0c;……);**【案例】**向goods表中插入一条新记录 步骤1&#xff…

密码学 | 椭圆曲线数字签名方法 ECDSA(上)

目录 1 ECDSA 是什么&#xff1f; 2 理解基础知识 3 为什么使用 ECDSA&#xff1f; 4 基础数学和二进制 5 哈希 6 ECDSA 方程 7 点加法 8 点乘法 9 陷阱门函数&#xff01; ⚠️ 原文&#xff1a;Understanding How ECDSA Protects Your Data. ⚠️ 写在前面…

Java+saas模式 智慧校园系统源码Java Android +MySQL+ IDEA 多校运营数字化校园云平台源码

Javasaas模式 智慧校园系统源码Java Android MySQL IDEA 多校运营数字化校园云平台源码 智慧校园即智慧化的校园&#xff0c;也指按智慧化标准进行的校园建设&#xff0c;按标准《智慧校园总体框架》中对智慧校园的标准定义是&#xff1a;物理空间和信息空间的有机衔接&#…

第七周学习笔记DAY.1-封装

学完本次课程后&#xff0c;你能够&#xff1a; 理解封装的作用 会使用封装 会使用Java中的包组织类 掌握访问修饰符&#xff0c;理解访问权限 没有封装的话属性访问随意&#xff0c;赋值也可能不合理&#xff0c;为了解决这些代码设计缺陷&#xff0c;可以使用封装。 面向…

【Linux系统编程】第四弹---基本指令(二)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、echo指令 2、cat指令 3、more指令 4、less指令 4、head指令 5、tail指令 6、时间相关的指令 7、cal指令 8、find指…

若依从0到1部署

服务器安装 MySQL8 Ubuntu 在 20.04 版本中&#xff0c;源仓库中 MySQL 的默认版本已经更新到 8.0&#xff0c;因此可以直接使用 apt-get 安装。 设置 apt 国内代理 打开 https://developer.aliyun.com/mirror/ 阿里云镜像站&#xff0c;找到适合自己的系统&#xff1a; 找…
最新文章