java设计模式-观察者模式
什么是观察者模式
观察者模式(Observer)是软件设计中的一种行为模式。
它定义了对象之间的一对多关系,其中如果一个对象改变了状态,所有依赖它的对象都会自动被通知并更新。
这种模式包含了两种主要的角色,即被观察者(Subject)和观察者(Observer)。
被观察者维护了一个观察者列表,并提供了注册和删除观察者的方法,当其状态发生变化时,会遍历观察者列表,通知所有观察者。
观察者则定义了一个更新接口,用于接收被观察者的通知并进行相应的更新操作。
这种模式能够使得对象之间的耦合度降低,同时也能够提高系统的灵活性和扩展性。
简单地说,观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监听一个主题对象。这样一来,当被观察者状态发生改变时,需要通知相应的观察者,使这些观察者对象能够自动更新。
应用场景
一个对象需要将自己的状态改变通知给其它多个对象,一个对象与它的多个依赖对象需要解耦,以便能对其修改,但不会对其它对象产生影响。
类图和角色

- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),维护对所有具体观察者的引用的列表,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,在被观察对象状态改变时会被调用。
- ConcreteObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
代码示例
模拟一个消息订阅推送的例子,有一个微信公众号服务,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息。
首先定义抽象主题(抽象被观察者)接口
/*** 抽象主题(抽象被观察者)接口** @author jiangkd* @date 2023/7/28 9:00:10*/
public interface ISubject {/*** 注册一个观察着** @param observer 观察者*/void registerObserver(IObserver observer);/*** 移除一个观察者** @param observer 观察者*/void removeObserver(IObserver observer);/*** 通知所有的观察着*/void notifyObservers();}
然后定义我们的抽象观察者接口
/*** 抽象观察者接口, 所有的观察者需要实现此接口** @author jiangkd* @date 2023/7/28 9:01:36*/
public interface IObserver {/*** 观察者对象接收到通知后的逻辑处理** @param msg 接收消息*/void update(String msg);}
接下来是具体主题(具体被观察者),也就是具体主题(被观察者)接口的实现类,实现了subject接口,对接口中的三个方法进行了实现,同时有一个List集合,用以保存注册的观察者,等需要通知观察者时,遍历该集合即可。
/*** 具体主题(具体被观察者)** @author jiangkd* @date 2023/7/28 10:00:57*/
@Component
public class WeChatMessage implements ISubject {private List<IObserver> observerList = new ArrayList<>();private String message;@Overridepublic void registerObserver(IObserver observer) {observerList.add(observer);}@Overridepublic void removeObserver(IObserver observer) {if (CollUtil.isNotEmpty(observerList)) {observerList.remove(observer);}}@Overridepublic void notifyObservers() {for (IObserver iObserver : observerList) {iObserver.update(message);}}/*** 模拟被观察者的主题更新, 通知所有观察者** @param message 主题更新*/public void updateMessage(String message) {this.message = message;// 通知所有观察者notifyObservers();}}
继续定义具体的观察者,这里我们模拟两个用户进行了订阅主题
/*** 具体的观察者1** @author jiangkd* @date 2023/7/28 10:07:34*/
@Slf4j
@Component
public class User1 implements IObserver {/*** 观察者接收消息, 知道被观察者发生了变化, 自己进行相应的处理, 这里只是测试打印日志而已** @param msg 接收消息*/@Overridepublic void update(String msg) {log.info("我是具体的观察者之一:{}", this.getClass().getSimpleName());log.info("被观察者发生变化, 接收消息:{}", msg);}}
/*** 具体的观察者2** @author jiangkd* @date 2023/7/28 10:09:22*/
@Slf4j
@Component
public class User2 implements IObserver {/*** 观察者接收消息, 知道被观察者发生了变化, 自己进行相应的处理, 这里只是测试打印日志而已** @param msg 接收消息*/@Overridepublic void update(String msg) {log.info("我是具体的观察者之一:{}", this.getClass().getSimpleName());log.info("被观察者发生变化, 接收消息:{}", msg);}}
最后测试一下
/*** @author jiangkd* @date 2023/7/28 10:10:34*/
@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
@Slf4j
public class ObserverTest {@ResourceWeChatMessage weChatMessage;@ResourceUser1 user1;@ResourceUser2 user2;@Testpublic void test(){// 绑定被观察者和观察者weChatMessage.registerObserver(user1);weChatMessage.registerObserver(user2);// 模拟被观察者发生变化weChatMessage.updateMessage("今天推送什么呢?");}}
执行结果如下:
2023-07-28 10:15:58.103 INFO 12236 — [ main] d.b.example.design_patterns.gczms.User1 : 我是具体的观察者之一:User1
2023-07-28 10:15:58.104 INFO 12236 — [ main] d.b.example.design_patterns.gczms.User1 : 被观察者发生变化, 接收消息:今天推送什么呢?
2023-07-28 10:15:58.104 INFO 12236 — [ main] d.b.example.design_patterns.gczms.User2 : 我是具体的观察者之一:User2
2023-07-28 10:15:58.104 INFO 12236 — [ main] d.b.example.design_patterns.gczms.User2 : 被观察者发生变化, 接收消息:今天推送什么呢?
记下来我们测试注销一个观察者,首先我们先再次添加一个观察者User3进行订阅主题
/*** 具体的观察者3** @author jiangkd* @date 2023/7/28 10:18:46*/
@Slf4j
@Component
public class User3 implements IObserver {/*** 观察者接收消息, 知道被观察者发生了变化, 自己进行相应的处理, 这里只是测试打印日志而已** @param msg 接收消息*/@Overridepublic void update(String msg) {log.info("我是具体的观察者之一:{}", this.getClass().getSimpleName());log.info("被观察者发生变化, 接收消息:{}", msg);}}
然后测试被观察者发生变化通知到三个订阅者,接着取消User2,只通知User1和User3
/*** @author jiangkd* @date 2023/7/28 10:10:34*/
@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
@Slf4j
public class ObserverTest {@ResourceWeChatMessage weChatMessage;@ResourceUser1 user1;@ResourceUser2 user2;@ResourceUser3 user3;@Testpublic void test2(){// 绑定被观察者和观察者weChatMessage.registerObserver(user1);weChatMessage.registerObserver(user2);weChatMessage.registerObserver(user3);// 模拟被观察者发生变化weChatMessage.updateMessage("今天推送什么呢?");log.info("================================================");// 注销其中一个观察者weChatMessage.removeObserver(user2);// 模拟被观察者发生变化weChatMessage.updateMessage("今天天气不错呢!");}}
运行结果日志记录如下
2023-07-28 10:23:21.663 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User1 : 我是具体的观察者之一:User1
2023-07-28 10:23:21.664 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User1 : 被观察者发生变化, 接收消息:今天推送什么呢?
2023-07-28 10:23:21.664 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User2 : 我是具体的观察者之一:User2
2023-07-28 10:23:21.664 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User2 : 被观察者发生变化, 接收消息:今天推送什么呢?
2023-07-28 10:23:21.664 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User3 : 我是具体的观察者之一:User3
2023-07-28 10:23:21.664 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User3 : 被观察者发生变化, 接收消息:今天推送什么呢?
2023-07-28 10:23:21.664 INFO 34500 — [ main] d.b.e.d.gczms.ObserverTest : ================================================
2023-07-28 10:23:21.669 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User1 : 我是具体的观察者之一:User1
2023-07-28 10:23:21.671 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User1 : 被观察者发生变化, 接收消息:今天天气不错呢!
2023-07-28 10:23:21.671 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User3 : 我是具体的观察者之一:User3
2023-07-28 10:23:21.671 INFO 34500 — [ main] d.b.example.design_patterns.gczms.User3 : 被观察者发生变化, 接收消息:今天天气不错呢!
相关文章:
java设计模式-观察者模式
什么是观察者模式 观察者模式(Observer)是软件设计中的一种行为模式。 它定义了对象之间的一对多关系,其中如果一个对象改变了状态,所有依赖它的对象都会自动被通知并更新。 这种模式包含了两种主要的角色,即被观察…...
HiveSQL SparkSQL中常用知识点记录
目录 0. 相关文章链接 1. hive中多表full join主键重复问题 2. Hive中选出最新一个分区中新增和变化的数据 3. Hive中使用sort_array函数解决collet_list列表排序混乱问题 4. SQL中对小数位数很多的数值转换成文本的时候不使用科学计数法 5. HiveSQL & SparkSQL中炸裂…...
mac不识别移动硬盘导致无法拷贝资源
背景 硬盘插入到Mac电脑上之后,mac不识别移动硬盘导致无法拷贝资源。 移动硬盘在Mac上无法被识别的原因可能有很多,多数情况下,是硬盘的格式与Mac电脑不兼容。 文件系统格式不兼容 macOS使用的文件系统是HFS或APFS,如果移动硬盘是…...
Opencv的Mat内容学习
来源:Opencv的Mat内容小记 - 知乎 (zhihu.com) 1.Mat是一种图像容器,是二维向量。 灰度图的Mat一般存放<uchar>类型 RGB彩色图像一般存放<Vec3b>类型。 (1)单通道灰度图数据存放样式: (2)RGB三通道彩色图存放形式不同&#x…...
MySQL~数据库的设计
二、数据库的设计 1、多表之间的关系 1.1 三种分类 一对一: 分析:一个人只有一个身份证,一个身份证只能对应一个人 如:人和身份证 一对多: 如:部门和员工 分析:一个部门有多个员工ÿ…...
开源了!最强原创图解八股文面试网来袭
强烈推荐 Github上业内新晋的一匹黑马—Java图解八股文面试网—Java2Top.cn,图解 Java 大厂面试题,深入全面,真的强烈推荐~ 这是一个二本逆袭阿里的大佬根据自己秋招上岸所看过的相关专栏,面经,课程,结合自…...
微信小程序开发6
一、分包-基础概念 1.1、什么是分包 分包指的是把一个完整的小程序项目,按照需求划分为不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。 1.2、分包的好处 对小程序进行分包的好处主要有以下两点: 可以优化小程序…...
JS 根据身份证号获取年龄、性别、出生日期
先说一代身份证和二代身份证的区别: 1.编号位数不同,第一代身份证为15位号码,第二代证是18位号码 2.编码规则不同,第一代身份证在前6位号码后没有完整出生年份,而二代的有完整的出生年份,一代身份证将年份前二位省略…...
Python+Mongo+LSTM(GTP生成)
下面是一个简单的示例来展示如何使用Python和MongoDB来生成LSTM预测算法。 首先,我们需要安装pymongo和tensorflow库,可以使用以下命令进行安装: pip install pymongo tensorflow接下来,我们连接到MongoDB数据库并获取需要进行预…...
关于idea如何成功运行web项目
导入项目 如图 依次选择 file - new - Project from Existing Sources 选择存放的项目目录地址 如图 导入完成 点击ok 如图 依次选择 Create project from existing sources 点击next如图 ,此处默认即可 点击 next如图 点击next有该提示 是因为之前导入过…...
python读取json文件
import json# 文件路径(同目录文件名即可,不同目录需要绝对路径) path 1.json# 读取JSON文件 with open(path, r, encodingutf-8) as file:data json.load(file)#data为字典 print(data) print(type(data))...
迁移学习、微调、计算机视觉理论(第十一次组会ppt)
@TOC 数据增广 迁移学习 微调 目标检测和边界框 区域卷积神经网络R—CNN...
特殊矩阵的压缩存储
1 数组的存储结构 1.1 一维数组 各数组元素大小相同,且物理上连续存放。第i个元素的地址位置是:a[i] LOC i*sizeof(ElemType) (LOC为起始地址) 1.2 二维数组 对于多维数组有行优先、列优先的存储方法 行优先:先行后列,先存储…...
【网络原理】 (1) (应用层 传输层 UDP协议 TCP协议 TCP协议段格式 TCP内部工作机制 确认应答 超时重传 连接管理)
文章目录 应用层传输层UDP协议TCP协议TCP协议段格式TCP内部工作机制确认应答超时重传 网络原理部分我们主要学习TCP/IP协议栈这里的关键协议(TCP 和 IP),按照四层分别介绍.(物理层,我们不涉及). 应用层 我们需要学会自定义一个应用层协议. 自定义协议的原因? 当前的软件(应用…...
【SQL语句】
目录 一、SQL语句类型 1.DDL 2.DML 3.DLL 4.DQL 二、数据库操作 1.查看 2.创建 2.1 默认字符集 2.2 指定字符集 3.进入 4.删除 5.更改 5.1 库名称 5.2 字符集 三、数据表操作 1.数据类型 1.1 数值类型(常见,下同) 1.1.1 T…...
自动驾驶和机器人学习和总结专栏汇总
汇总如下: 一. 器件选型心得(系统设计)--1_goldqiu的博客-CSDN博客 一. 器件选型心得(系统设计)--2_goldqiu的博客-CSDN博客 二. 多传感器时间同步方案(时序闭环)--1 三. 多传感器标定方案&…...
【C++初阶】C++基础(下)——引用、内联函数、auto关键字、基于范围的for循环、指针空值nullptr
目录 1. 引用 1.1 引用概念 1.2 引用特性 1.3 常引用 1.4 使用场景 1.5 传值、传引用效率比较 1.6 引用和指针的区别 2. 内联函数 2.1 概念 2.2 特性 3.auto关键字(C11) 3.1 类型别名思考 3.2 auto简介 3.3 auto的使用细则 3.4 auto不能推…...
OSI 7层模型 TCPIP四层模型
》Ref: 1. 这个写的嘎嘎好,解释了为啥4层7层5层,还有数据包封装的问题:数据包在网络中的传输过程详解_数据包传输_张孟浩_jay的博客-CSDN博客 2. HTTP协议 与 TCP协议 的区别,作为web程序员必须要懂 - 知乎 (zhihu.com) 3. 数据…...
iOS-持久化
目的 1.快速展示,提升体验 已经加载过的数据,用户下次查看时,不需要再次从网络(磁盘)加载,直接展示给用户 2.节省用户流量(节省服务器资源) 对于较大的资源数据进行缓存…...
PC音频框架学习
1.整体链路 下行播放: App下发音源→CPU Audio Engine 信号处理→DSP数字信号处理→Codec DAC→PA→SPK 上行录音: MIC拾音→集成运放→Codec ADC→DSP数字信号处理→CPU Audio Engine 信号处理→App 2.硬件 CPU PCH DSP(可选) Codec PA SPKbox MIC…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
