设计模式——观察者设计模式(行为型)
摘要
本文详细介绍了观察者设计模式,包括其定义、结构、实现方式、适用场景以及实战示例。通过代码示例展示了如何在Spring框架下实现观察者模式,以及如何通过该模式实现状态变化通知。同时,对比了观察者模式与消息中间件在设计理念、耦合程度、通信方式和分布式支持等方面的差异,帮助读者更好地理解和选择合适的实现方式。
1. 观察者设计模式定义
观察者设计模式(Observer Pattern)是一种行为型设计模式,定义了一种一对多的依赖关系,使得当一个对象(被观察者)的状态发生改变时,所有依赖它的对象(观察者)都会得到自动通知并更新,从而实现对象间的松耦合和实时通讯。观察者模式通过定义对象间的发布-订阅关系,实现事件的自动通知和响应,适合事件驱动和异步消息场景。
1.1. 核心定义
角色
- 被观察者(Subject):维护一组观察者,负责状态的管理和通知。
- 观察者(Observer):注册到被观察者,一旦被观察者状态变化,接收通知并执行相应动作。
目的:让多个观察者对象自动获得被观察者的状态变化,降低对象间耦合度。
2. 观察者设计模式结构
观察者模式包含如下角色:
- Subject: 目标
- ConcreteSubject: 具体目标
- Observer: 观察者
- ConcreteObserver: 具体观察者
2.1. 观察者模式类图
2.2. 观察者模式时序图
3. 观察者设计模式实现方式
3.1. 观察者设计模式实现步骤
3.1.1. 定义抽象主题(Subject)接口
- 提供注册、移除和通知观察者的方法。
public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();
}
3.1.2. 定义抽象观察者(Observer)接口
- 声明一个更新方法,主题状态变化时调用。
public interface Observer {void update(String message);
}
3.1.3. 具体主题类实现 Subject
- 维护观察者列表,实现注册、移除、通知逻辑。
- 当自身状态变化时调用
notifyObservers()
,遍历通知所有观察者。
import java.util.ArrayList;
import java.util.List;public class ConcreteSubject implements Subject {private final List<Observer> observers = new ArrayList<>();private String state;@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(state);}}public void setState(String state) {this.state = state;notifyObservers();}
}
3.1.4. 具体观察者实现 Observer
- 实现
update
方法,接收通知并做出响应。
public class ConcreteObserver implements Observer {private String name;public ConcreteObserver(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " 收到通知,状态变为: " + message);}
}
3.2. 观察者测试示例
public class ObserverPatternDemo {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();Observer observer1 = new ConcreteObserver("观察者1");Observer observer2 = new ConcreteObserver("观察者2");subject.registerObserver(observer1);subject.registerObserver(observer2);subject.setState("状态1");subject.setState("状态2");}
}
说明
- 主题(Subject)维护观察者集合,状态变化时通知所有观察者。
- 观察者实现更新接口,获得主题最新状态。
4. 观察者设计模式适合场景
4.1. ✅ 适合使用观察者设计模式的场景
适合场景 | 说明 |
事件驱动系统 | 事件产生后需要通知多个模块或对象做出响应,如 UI 事件监听、消息推送。 |
一对多依赖关系 | 一个对象状态变化需要通知多个依赖对象,如股票价格更新通知所有订阅者。 |
松耦合需求 | 希望对象间解耦,观察者与被观察者不直接依赖,实现灵活扩展。 |
广播通信 | 需要将消息广播给多个接收者,且接收者可动态增加和移除。 |
异步通知 | 状态更新后,通知观察者进行异步处理。 |
分布式系统 | 多个服务或模块之间的状态同步与消息推送。 |
4.2. ❌ 不适合使用观察者设计模式的场景
不适合场景 | 说明 |
对象之间不存在依赖关系 | 彼此独立的对象无需相互通知。 |
通知对象数量固定且简单 | 只有少数固定对象,且耦合关系明确,使用简单调用即可。 |
需要严格同步控制的场景 | 观察者通知是异步的,无法满足严格同步时序需求。 |
性能敏感场景 | 大量观察者通知导致性能开销大,影响系统响应速度。 |
过度使用导致复杂性增加 | 观察者链条过长,维护困难,代码难以理解。 |
总结:观察者模式主要适用于需要“自动广播状态变化给多个对象”且“希望对象间低耦合”的场景。不适合简单调用、同步严格或性能瓶颈明显的场景。
5. 观察者设计模式实战示例
5.1. 场景说明
在风控系统中,当订单状态发生变化(比如风控审核结果出来后),需要通知多个模块(比如日志记录模块、报警模块、缓存更新模块)来响应该状态变化。
5.2. 定义观察者接口
public interface OrderStatusObserver {void update(String orderId, String status);
}
5.3. 定义具体观察者实现类
import org.springframework.stereotype.Component;@Component
public class LoggingObserver implements OrderStatusObserver {@Overridepublic void update(String orderId, String status) {System.out.println("日志模块:订单 " + orderId + " 状态更新为:" + status);// 这里可以写日志持久化操作}
}@Component
public class AlertObserver implements OrderStatusObserver {@Overridepublic void update(String orderId, String status) {if ("REJECTED".equals(status)) {System.out.println("报警模块:订单 " + orderId + " 审核拒绝,触发报警!");// 触发报警逻辑}}
}@Component
public class CacheObserver implements OrderStatusObserver {@Overridepublic void update(String orderId, String status) {System.out.println("缓存模块:更新订单 " + orderId + " 状态缓存为:" + status);// 缓存刷新逻辑}
}
5.4. 定义主题接口和实现类
public interface OrderStatusSubject {void registerObserver(OrderStatusObserver observer);void removeObserver(OrderStatusObserver observer);void notifyObservers(String orderId, String status);
}
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;@Component
public class OrderStatusSubjectImpl implements OrderStatusSubject {private final List<OrderStatusObserver> observers = new ArrayList<>();// 使用Spring自动注入所有实现了OrderStatusObserver接口的Bean@Autowiredprivate List<OrderStatusObserver> observerBeans;@PostConstructpublic void init() {// 初始化时,将所有观察者注册进列表observers.addAll(observerBeans);}@Overridepublic void registerObserver(OrderStatusObserver observer) {observers.add(observer);}@Overridepublic void removeObserver(OrderStatusObserver observer) {observers.remove(observer);}@Overridepublic void notifyObservers(String orderId, String status) {for (OrderStatusObserver observer : observers) {observer.update(orderId, status);}}
}
5.5. 4. 业务调用示例(比如风控服务)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class RiskControlService {@Autowiredprivate OrderStatusSubject orderStatusSubject;public void auditOrder(String orderId, boolean passed) {// 风控业务逻辑String status = passed ? "APPROVED" : "REJECTED";System.out.println("风控审核完成,订单:" + orderId + ",状态:" + status);// 审核完成后,通知所有观察者orderStatusSubject.notifyObservers(orderId, status);}
}
5.6. 测试Controller(可选)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class RiskController {@Autowiredprivate RiskControlService riskControlService;@PostMapping("/auditOrder")public String auditOrder(@RequestParam String orderId, @RequestParam boolean passed) {riskControlService.auditOrder(orderId, passed);return "审核完成";}
}
5.7. 实战示例总结
- 观察者接口
OrderStatusObserver
,多个实现类由Spring管理并注入。 - 主题
OrderStatusSubjectImpl
维护观察者列表,利用@Autowired
自动注入所有观察者Bean。 - 风控业务执行后调用主题的
notifyObservers()
,实现状态变化通知。 - 全部采用Spring注解管理,未用构造函数注入,代码简洁且易于扩展。
6. 观察者设计模式思考
6.1. 观察着模式和使用消息中间件来通知处理的方式有什么不同?
很好的问题!观察者模式和消息中间件在实现“发布-订阅”或通知机制时都很常用,但它们的设计理念、实现方式和适用场景有显著差别。下面帮你详细分析两者的区别:
维度 | 观察者模式 | 消息中间件(如Kafka、RabbitMQ、ActiveMQ等) |
设计层次 | 设计模式,属于应用内部的设计范式,通常是在代码内部直接实现。 | 独立的中间件系统,属于基础设施层,提供异步消息传递机制。 |
耦合程度 | 被观察者和观察者耦合较紧密,观察者必须知道被观察者实例(尽管解耦设计可以降低耦合)。 | 发布者和订阅者完全解耦,彼此不直接依赖,只通过消息中间件通信。 |
通信方式 | 通常是同步调用,状态变化时立即调用观察者的更新方法。 | 典型异步通信,消息发送后,接收方异步消费,支持异步处理和缓冲。 |
分布式支持 | 一般用于单体应用或同一JVM内对象之间的通知。 | 天生支持分布式系统,跨进程、跨服务器,甚至跨数据中心的消息传递。 |
扩展性 | 观察者数量有限,扩展时需要修改代码或依赖注入配置。 | 高度扩展,支持大量消费者和复杂路由规则,方便动态添加消费者。 |
容错与持久化 | 无持久化和容错机制,一旦通知失败可能丢失信息。 | 支持消息持久化、重试、消息确认、死信队列,增强系统可靠性。 |
性能影响 | 观察者调用是同步的,影响调用链的执行时间,可能导致性能瓶颈。 | 异步处理,解耦主业务流程,提升系统吞吐量和响应速度。 |
使用场景 | 小型系统、UI事件响应、简单状态同步、模块内部事件通知。 | 大型分布式系统、微服务间异步通信、异步任务调度、事件驱动架构。 |
6.1.1. 简单总结:
- 观察者模式 适合应用内、轻量级的同步通知,设计简单,适合对象之间状态变化通知。
- 消息中间件 适合跨进程、跨网络、异步且高可靠的消息通信,适合分布式系统异步解耦和复杂事件流转。
6.1.2. 举例对比:
- 观察者模式:风控模块风控结果出来后,同步通知日志模块和报警模块,立即处理。
- 消息中间件:风控模块发布“风控结果”消息到消息队列,日志模块和报警模块异步订阅该消息,消息队列保证消息可靠送达和解耦。
博文参考
- 3. 观察者模式 — Graphic Design Patterns
- 观察者设计模式
相关文章:

设计模式——观察者设计模式(行为型)
摘要 本文详细介绍了观察者设计模式,包括其定义、结构、实现方式、适用场景以及实战示例。通过代码示例展示了如何在Spring框架下实现观察者模式,以及如何通过该模式实现状态变化通知。同时,对比了观察者模式与消息中间件在设计理念、耦合程…...
【前端】Vue中使用CKeditor作为富文本编辑器
官网https://ckeditor.com/ 此处记录一下我在使用的时候具体初始化的代码。 <template><div><textarea :id"id"></textarea></div> </template><script> export default {name: CkEditor,data: function () {return {id:…...
CSS篇-6
1. 如果将<html>元素的font-size设置为10rem,那么当用户调整或拖曳浏览器窗口时,其文本大小会受到影响吗? 不会受到影响。rem单位是相对于根元素(即<html>元素)的font-size计算的。一旦<html>的fon…...

【计算机系统结构】习题2
目录 1.有一条静态多功能流水线由5段组成,加法用1、2、4、5段,乘法用1、3、5段,第3段时间为,其余各段为,且流水线的输出可直接返回输入端或暂存器,若计算,试计算吞吐量、加速比、效率 2.有一动…...

用户资产化视角下开源AI智能名片链动2+1模式S2B2C商城小程序的应用研究
摘要:在数字化时代,平台流量用户尚未完全转化为企业的数字资产,唯有将其沉淀至私域流量池并实现可控、随时触达,方能成为企业重要的数字资产。本文从用户资产化视角出发,探讨开源AI智能名片链动21模式S2B2C商城小程序在…...
day023-面试题总结
文章目录 1. 运维基础面试题1.1 物理机没有正常启动,进入紧急模式,你的排查思路是什么1.2 系统优化什么1.3 印象比较深刻的故障1.4 在现场环境网络方面有遇到什么问题吗1.5 kylin操作系统部署服务遇到过哪些问题?1.6 单独做项目一般几台服务器…...

机器学习实验七--SVM垃圾邮件分类器
SVM垃圾邮件分类器 一、什么是SVM二、实例:垃圾邮件分类器1.实验要求2.原理解释2.1 数据预处理流程2.2 特征提取方法2.3 SVM分类器 3.代码实现4.实验结果5.实验总结 一、什么是SVM 支持向量机(Support Vector Machine, SVM)是一种监督学习算法,主要用于…...

C++23 std::fstreams基础回顾
文章目录 引言1.1 std::fstreams概述1.2 std::fstreams的主要功能和常用操作 2. 独占模式 (P2467R1) 的详细介绍2.1 独占模式的定义和背景2.2 独占模式的作用和优势 3. C23 std::fstreams支持独占模式 (P2467R1) 的具体实现方式3.1 代码示例3.2 实现步骤解释 4. 使用该特性可能…...

Git初识Git安装
目录 1. Git初识 1.1 提出问题 1.2 如何解决--版本控制器 1.3 注意事项 2 Git安装 2.1 Centos 2.2 Ubuntu 2.3 Windows 1. Git初识 1.1 提出问题 不知道你工作或学习时,有没有遇到这样的情况:我们在编写各种文档时,为了防止文档丢失…...

使用Redisson实现分布式锁发现的【订阅超时】Subscribe timeout: (7500ms)
背景 使用 redisson 实现分布式锁,出现的异常: org.redisson.client.RedisTimeoutException: Subscribe timeout: (7500ms). Increase ‘subscriptionsPerConnection’ and/or ‘subscriptionConnectionPoolSize’ parameters 从异常信息读的出来一些东…...
数据分析的方法总结
数据分析的方法总结 一.通用性方法总结 16种常用的数据分析方法汇总-CSDN博客 人人都应该掌握的9种数据分析方法_五 九种数据类型-CSDN博客 9种最常用数据分析方法,解决90%分析难题_数据分析经典算法与案例-CSDN博客 二.行业特殊性方法总结 数据分析20大基本分…...

如何使用 poetry 创建虚拟环境,VSCode 如何激活使用 Poetry 虚拟环境(VSCode如何配置 Poetry 虚拟环境)
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 使用 Poetry 创建和激活虚拟环境 📒🧪 创建项目并初始化 Poetry🔧 配置虚拟环境创建位置✅ 指定Python版本📦 安装依赖并创建虚拟环境🚀 激活虚拟环境📒 在 VSCode 中配置 Poetry 虚拟环境 📒🧭 方法一:使用 V…...
每天掌握一个Linux命令 - ps
Linux 命令工具 ps 与 pstree 详解 一、ps 工具概述 ps(Process Status)是 Linux 系统中用于查看当前进程状态的核心工具,可显示进程的 PID、用户、CPU 占用率、内存使用量、启动时间、命令行参数等信息。 应用场景:监控系统性…...

牛客小白月赛117
前言:solveABCF相对简单,D题思路简单但是实现麻烦,F题郭老师神力b( ̄▽ ̄)。 A. 好字符串 题目大意:给定字符串s,里面的字母必须大小写同时出现。 【解题】:没什么好说的࿰…...
浅谈 Linux 文件覆盖机制
引言:文件覆盖的本质 文件覆盖是 Linux 文件系统中常见的操作,指将源文件内容写入目标路径,导致目标文件原有内容被替换或新文件被创建。覆盖操作通常通过命令行工具(如 mv、cp)或系统调用(如 open() 以写…...

美化显示GDB调试的数据结构
笔者在前面的博文记一次pdf转Word的技术经历中有使用到mupdf库,该库是使用C语言写的一个操作PDF文件的库,同时提供了Python接口,Java接口和JavaScript接口。 在使用该库时,如果想要更高的性能,使用C语言接口是不二的选…...

一篇学习CSS的笔记
一、简介 Cascading Style Sheets简称CSS,中文翻译为层叠样式表。当HTML被发明出来初期,不同的浏览器提供了各种各样的样式语言给用户控制网页的效果,HTML包含的显示属性并不是很多。但是随着各种使用者对HTML的需求,HTML添加了大…...
Rust 学习笔记:自定义构建和发布配置
Rust 学习笔记:自定义构建和发布配置 Rust 学习笔记:自定义构建和发布配置发布配置文件自定义 profile 的选项 Rust 学习笔记:自定义构建和发布配置 发布配置文件 在 Rust 中,发布配置文件是预定义的和可定制的概要文件…...

StarRocks x Iceberg:云原生湖仓分析技术揭秘与最佳实践
导读: 本文将深入探讨基于 StarRocks 和 Iceberg 构建的云原生湖仓分析技术,详细解析两者结合如何实现高效的查询性能优化。内容涵盖 StarRocks Lakehouse 架构、与 Iceberg 的性能协同、最佳实践应用以及未来的发展规划,为您提供全面的技术解…...

笔试笔记(运维)
(数据库,SQL) limit1 随机返回其中一个聚合函数不可以嵌套使用 【^】这个里面的数据任何形式组合都没有 sql常用语句顺序:from-->where-->group by-->having-->select-->order by-->limit 只要其中一个表存在匹…...
JVM——云原生时代JVM的演进之路
引入 在风云变幻的技术世界里,JVM(Java Virtual Machine)作为 Java 语言的基石,长久以来承载着无数开发者构建软件系统的梦想。从 20 世纪 90 年代 Java 的诞生,到如今云原生时代的大幕拉开,JVM 经历了岁月…...

使用langchain实现五种分块策略:语义分块、父文档分块、递归分块、特殊格式、固定长度分块
文章目录 分块策略详解1. 固定长度拆分(简单粗暴)2. 递归字符拆分(智能切割)3. 特殊格式拆分(定向打击)Markdown分块 4. 语义分割(更智能切割)基于Embedding的语义分块基于模型的端到…...

【项目记录】登录认证(下)
1 过滤器 Filter 刚才通过浏览器的开发者工具,可以看到在后续的请求当中,都会在请求头中携带JWT令牌到服务端,而服务端需要统一拦截所有的请求,从而判断是否携带的有合法的JWT令牌。 那怎么样来统一拦截到所有的请求校验令牌的有…...
Debian上安装PostgreSQL的故障和排除
命令如下: apt install postgresql#可能是apt信息错误,报错 E: Failed to fetch http://deb.debian.org/debian/pool/main/p/postgresql-15/postgresql-client-15_15.12-0%2bdeb12u2_amd64.deb 404 Not Found [IP: 146.75.46.132 80] E: Failed to f…...

linux文件管理(补充)
1、查看文件命令 1.1 cat 用于连接文件并打印到标准输出设备上,它的主要作用是用于查看和连接文件。 用法: cat 参数 文件名 参数: -n:显示行号,会在输出的每一行前加上行号。 -b:显示行号,…...

Python训练营---Day42
DAY 42 Grad-CAM与Hook函数 知识点回顾 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业:理解下今天的代码即可 1、回调函数 回调函数(Callback Function)是一种特殊的函数,它作为参数传递给另一个函数&#…...

基于空天地一体化网络的通信系统matlab性能分析
目录 1.引言 2.算法仿真效果演示 3.数据集格式或算法参数简介 4.MATLAB核心程序 5.算法涉及理论知识概要 5.1 QPSK调制原理 5.2 空天地一体化网络信道模型 5.3 空天地一体化网络信道特性 6.参考文献 7.完整算法代码文件获得 1.引言 空天地一体化网络是一种将卫星通信…...

c++ opencv 形态学操作腐蚀和膨胀
https://www.jb51.net/article/247894.htm(上图图片来自这个博客) https://codec.wang/docs/opencv/basic/erode-and-dilate(上图图片参考博客) cv::Mat kernel cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::erode(src, dst, kern…...
Axure组件即拖即用:横向拖动菜单(支持左右拖动选中交互)
亲爱的小伙伴,在您浏览之前,请关注一下,在此深表感谢!如有帮助请订阅专栏!免费哦! Axure横向菜单拖不动?一拖就乱?你缺的是这个"防手残"组件! 💢…...
Hadoop MapReduce:大数据处理利器
Hadoop 的 MapReduce 是一种用于处理大规模数据集的分布式计算框架,基于“分而治之”思想设计。以下从核心概念、工作流程、代码结构、优缺点和应用场景等方面详细讲解: 一、MapReduce 核心概念 核心思想: Map࿰…...