23种设计模式(三)——观察者模式【组件协作】

news2025/1/7 18:28:07

文章目录

    • 意图
    • 什么时候使用观察者
        • 使用观察者模式也有两个重点问题要解决:
          • 1)广播链的问题
          • 2)异步处理问题
        • 真实世界类比
    • 观察者模式的实现
    • 观察者模式的优缺点

亦称:事件订阅者、监听者、Event-Subscriber、Listener、Observer

意图

在许多设计中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化,也就是说当对象间存在一对多关系时,在这样的情况下就可以使用观察者模式。当一个对象被修改时,则会自动通知它的依赖对象。

观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟的模式。观察者模式中有一个称作“主题”的对象和若干个称作“观察者”的对象,“主题”和“观察者”间是一种一对多的依赖关系,当“主题”的状态发生变化时,所有“观察者”都得到通知。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

在这里插入图片描述

什么时候使用观察者

1、当一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合。

2、当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象需要更新数据。

使用观察者模式也有两个重点问题要解决:

1)广播链的问题

如果你做过数据库的触发器,你就应该知道有一个触发器链的问题,比如表 A 上写了一个触发器,内容是一个字段更新后更新表 B 的一条数据,而表 B 上也有个触发器,要更新表 C,表 C 也有触发器…,完蛋了,这个数据库基本上就毁掉了!我们的观察者模式也是一样的问题,一个观察者可以有双重身份,即使观察者,也是被观察者,这没什么问题呀,但是链一旦建立,这个逻辑就比较复杂,可维护性非常差,根据经验建议,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),这还是比较好控制的;

2)异步处理问题

被观察者发生动作了,观察者要做出回应,如果观察者比较多,而且处理时间比较长怎么办?那就用异步呗,异步处理就要考虑线程安全和队列的问题,这个大家有时间看看消息队列(Message Queue),就会有更深的了解。

真实世界类比

在这里插入图片描述

杂志和报纸订阅。

如果你订阅了一份杂志或报纸, 那就不需要再去报摊查询新出版的刊物了。 出版社 (即应用中的 “发布者”) 会在刊物出版后 (甚至提前) 直接将最新一期寄送至你的邮箱中。

出版社负责维护订阅者列表, 了解订阅者对哪些刊物感兴趣。 当订阅者希望出版社停止寄送新一期的杂志时, 他们可随时从该列表中退出。

观察者模式的实现

1、IObserver类—抽象观察者,为所有具体观察者定义一个接口,在得到主题通知时更新自己。

这个接口叫做更新接口,抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包括一个Update方法,这个方法叫做更新方法。

2、ISubject类—主题或者抽象通知者,一般用一个抽象类或者一个接口实现。

它把所有对观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者。

3、Subject类—具体主题或者具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发送通知。具体主题角色通常用一个具体类实现。

4、Observer类—具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。

具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体类实现。

class IObserver {
public:
	virtual ~IObserver() {};
	virtual void Update(const std::string &message_from_subject) = 0;
};

class ISubject {
public:
	virtual ~ISubject() {};
	virtual void Attach(IObserver *observer) = 0;
	virtual void Detach(IObserver *observer) = 0;
	virtual void Notify() = 0;
};

/**
 * The Subject owns some important state and notifies observers when the state
 * changes.
 */

class Subject : public ISubject {
public:
	virtual ~Subject() {
		std::cout << "Goodbye, I was the Subject.\n";
	}

	/**
	 * The subscription management methods.
	 */
	void Attach(IObserver *observer) override {
		list_observer_.push_back(observer);
	}
	void Detach(IObserver *observer) override {
		list_observer_.remove(observer);
	}
	void Notify() override {
		std::list<IObserver *>::iterator iterator = list_observer_.begin();
		HowManyObserver();
		while (iterator != list_observer_.end()) {
			(*iterator)->Update(message_);
			++iterator;
		}
	}

	void CreateMessage(std::string message = "Empty") {
		this->message_ = message;
		Notify();
	}
	void HowManyObserver() {
		std::cout << "There are " << list_observer_.size() << " observers in the list.\n";
	}

	/**
	 * Usually, the subscription logic is only a fraction of what a Subject can
	 * really do. Subjects commonly hold some important business logic, that
	 * triggers a notification method whenever something important is about to
	 * happen (or after it).
	 */
	void SomeBusinessLogic() {
		this->message_ = "change message message";
		Notify();
		std::cout << "I'm about to do some thing important\n";
	}

private:
	std::list<IObserver *> list_observer_;
	std::string message_;
};

class Observer : public IObserver {
public:
	Observer(Subject &subject) : subject_(subject) {
		this->subject_.Attach(this);
		std::cout << "Hi, I'm the Observer \"" << ++Observer::static_number_ << "\".\n";
		this->number_ = Observer::static_number_;
	}
	virtual ~Observer() {
		std::cout << "Goodbye, I was the Observer \"" << this->number_ << "\".\n";
	}

	void Update(const std::string &message_from_subject) override {
		message_from_subject_ = message_from_subject;
		PrintInfo();
	}
	void RemoveMeFromTheList() {
		subject_.Detach(this);
		std::cout << "Observer \"" << number_ << "\" removed from the list.\n";
	}
	void PrintInfo() {
		std::cout << "Observer \"" << this->number_ << "\": a new message is available --> " << this->message_from_subject_ << "\n";
	}

private:
	std::string message_from_subject_;
	Subject &subject_;
	static int static_number_;
	int number_;
};

观察者模式的优缺点

优点缺点
具体主题和具体观察者是松耦合关系。如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
观察者模式满足“开-闭原则”。主题接口仅仅依赖于观察者接口。如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

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

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

相关文章

mybatis之动态SQL测试环境的搭建以及if语句的使用

动态SQL&#xff1a; 动态 SQL 是 MyBatis 的强大特性之一&#xff0c;如果你使用过 JDBC 或其它类似的框架&#xff0c;你应该能理解根据不同条件拼接 SQL 语句有多痛苦&#xff0c;例如拼接时要确保不能忘记添加必要的空格&#xff0c;还要注意去掉列表最后一个列名的逗号&a…

Vue CLI

介绍 Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统&#xff0c;提供&#xff1a; 通过 vue/cli 实现的交互式的项目脚手架。 通过 vue/cli vue/cli-service-global实现的零配置原型开发。 一个运行时依赖 (vue/cli-service)&#xff0c;该依赖&#xff1a; 可升级&a…

腾讯安全发布《2022年DDoS攻击威胁报告》:DDoS威胁4年持续增长

随着全球数字化蓬勃发展&#xff0c;互联网的应用范围不断扩大&#xff0c;并逐渐普及到各行各业的生产、管理、运营等方面&#xff0c;网络设备可用带宽伴随应用需求的增加而增加&#xff0c;方便了企业业务开展的同时也扩大了安全威胁面&#xff0c;引来黑产的觊觎。DDoS攻击…

Java使用流去除集合中某个字段为空的对象

文章目录0 写在前面1 情景复刻2 解决方案3 写在最后0 写在前面 最近写了一些业务逻辑&#xff0c;调试的时候总会报空指针异常。 Java中空指针异常是危险恐怖分子&#xff0c;最好不要碰见他。所以有些时候&#xff0c;处理集合中的数据时&#xff0c;特定情况下需要略过一些数…

十五天学会Autodesk Inventor,看完这一系列就够了(二),软件界面

众所周知&#xff0c;Autocad是一款用于二维绘图、详细绘制、设计文档和基本三维设计&#xff0c;现已经成为国际上广为流行的绘图工具。Autodesk Inventor软件也是美国AutoDesk公司推出的三维可视化实体模拟软件。因为很多人都熟悉Autocad&#xff0c;所以再学习Inventor&…

RK3568工业级核心板高温运行测试

Rockchip RK3568 是一款通用型MPU&#xff0c;产品集成GPU、NPU&#xff0c;支持4K、HDMI、LVDS、MIPI、PCIe3.0、USB3.0、千兆以太网、CAN-BUS、UART等丰富外设接口。 RK3568的高温工作情况如何呢&#xff1f;本文将基于万象奥科HD-RK3568-CORE 系列核心板做详细高温测试&…

接口幂等性设计

幂等性: 对于同一个操作发起一次请求或者多次请求&#xff0c;得到的结果都是一样的&#xff0c;不会因为请求多次而出现异常现象。 场景: 用户多次请求&#xff0c;比如重复点击页面上的按钮网络异常&#xff0c;右移网络原因导致在一定时间内未返回调用成功的信息&#xff…

《JavaScript 核心原理解析》学习笔记 Day 1 delete 引用与值

关于引用与值&#xff1a;在 javaScript 中一个表达式的值或者说结果&#xff0c;可能是引用 / 值。所以 x x &#xff0c;是将右侧表达式x的值赋值给左侧表达式x所指的引用。注意此处的引用并非为到具体内存地址的指向&#xff0c;而是指表达式与其值的一种关联。 这一关联即…

Android 音视频——直播推流技术指南

一、推流架构 推流SDK客户端的模块主要有三个&#xff0c;推流采集端、队列控制模块、推流端。其中每个模块的主要流程如下&#xff0c;本文的主要目的就是拆分推流流程&#xff0c; 1.1 采集端 视频采集&#xff1a;通过Camera采集视频。 音频采集&#xff1a;通过麦克风采…

SSM 05 SpringBoot yaml mybatisplus

01-SpringBoot工程入门案例开发步骤SpringBoot 是 Pivotal 团队提供的全新框架&#xff0c;设计目的是简化 Spring 应用的初始搭建以及开发过程。使用了 Spring 框架后已经简化了我们的开发。而 SpringBoot 又是对 Spring 开发进行简化的&#xff0c;可想而知 SpringBoot使用的…

linux挂载新磁盘

一、查看磁盘挂载状态&#xff1a; fdisk -l df -h 二、为其中一个磁盘创建新的分区&#xff0c;参考&#xff1a; linux用fdisk创建分区,在Linux下用fdisk创建分区_weixin_39968410的博客-CSDN博客 sudo fdisk /dev/nvme0n1 1. 创建主分区&#xff1a; -----------------…

第8章 NVS

NVS Blob块存储 1. 演示app_main任务栈溢出 2. 设置app_main任务栈大小 打开menuconfig&#xff0c;输入main&#xff0c;如下图所示 默认栈大小为3584字节&#xff0c;这里改为35840字节&#xff0c;重新编译 3. Blob存储结果 #include <stdio.h> #include <st…

使用nginx搭建HTTP FLV流媒体服务器

使用nginx搭建HTTP FLV流媒体服务器 文章目录使用nginx搭建HTTP FLV流媒体服务器1 HTTP FLV简介2 HTTP FLV流媒体服务搭建3 结果验证1 HTTP FLV简介 前文已经介绍了RTSP、RTMP、HLS的流媒体协议&#xff0c;还有一种比较常见的流媒体协议HTTP FLV&#xff0c;其兼具RTMP的实时…

Kettle源码启动运行

Kettle源码运行环境如下&#xff1a; windows10 Kettle 9.3.0.2 Java JDK 11 IntelliJ IDEA 2021.2.2 (Community Edition) Maven 3.8.1&#xff08;版本不需要太高 &#xff09; 导入kettle到IDEA 可通过kettle的GIthub地址获取 kettle的克隆连接&#xff0c;或直接下载ZIP压…

python2和python3环境安装

一、背景 ​ 众所周知&#xff0c;python当前有两大主流版本&#xff0c;分别是Python2和Python3系列&#xff0c;其中Python3因为对Python2做了较大的优化&#xff0c;使得Python3不会向下兼容&#xff0c;但是工作和学习中&#xff0c;有很多项目需要Python2的环境&#xff…

SAP 物料账未分摊差异分析

今天在开发处理未分摊差异程序的时候&#xff0c;偶然在网络上看到一篇这样的文章&#xff0c;挺有意思的&#xff0c;特意转载过来&#xff0c;方便大伙学习之用&#xff0c;若有异议&#xff0c;立即撤回。 利用CKMLCP运行完物料分类账之后&#xff0c;差异科目余额通常为0&…

Golang网络聊天室案例

1.聊天室设计分析 一. 概览 实现 个网络聊天室&#xff08;群&#xff09; 功能分析&#xff1a; 上线下线聊天&#xff0c;其他人&#xff0c;自己都可以看到聊天消息查询当前聊天室用户名字 who可以修改自己名字 rename | Duke超时踢出 技术点分析&#xff1a; 1 . sock …

Notion 汉化Macwindows客户端

1、注册/登录账号&#xff1a; https://www.notion.so/zh-cn 2、下载桌面应用&#xff1a; https://www.notion.so/desktop 3、下载汉化js插件 地址&#xff1a;https://github.com/Reamd7/notion-zh_CN 点击最后一次更新的标签&#xff0c;下载【 notion-zh_CN.js 】文件 …

[激光原理与应用-64]:激光器-器件 - 光电二极管

第1章 概述光电二极管&#xff08;Photo-Diode&#xff09;和普通二极管一样&#xff0c;也是由一个PN结组成的半导体器件&#xff0c;也具有单方向导电特性。但在电路中它不是作整流元件&#xff0c;而是把光信号转换成电信号的光电传感器件。普通二极管在反向电压作用时处于截…

如何在Windows上同时搭建多个版本的golang环境——g

假如说我们不同的项目使用的go版本是不一样的&#xff0c;当我们想切换时&#xff0c;不懂的人可能就直接卸载掉现有的go环境去安装新的go环境了&#xff0c;这种方法可行但是有点呆&#xff0c;今天推荐一个好用的go版本管理工具——g,不是寄了的寄&#xff0c;就是英文字母g。…