观察者模式的思考
观察者模式由来
观察者模式(Observer Pattern)是一种行为型设计模式,它的起源可以追溯到20世纪90年代初,由设计模式四人帮(Erich Gamma, Richard Helm, Ralph Johnson 和 John Vlissides)在其著作《设计模式:可复用面向对象软件的基础》中首次提出。观察者模式用于解决对象之间的一对多依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。
概念
- 被观察者(Subject):定义一个接口,用于添加、删除和通知观察者。
- 观察者(Observer):定义一个接口,用于接收被观察者的通知并执行相应的操作。
- 具体被观察者(ConcreteSubject):实现被观察者接口,维护观察者列表,并在状态改变时通知所有观察者。
- 具体观察者(ConcreteObserver):实现观察者接口,具体实现接收到通知后的操作。

实现原理
观察者模式的核心原理是通过将对象间的依赖关系从硬编码转移到外部,使得一个对象(被观察者)可以在不通知其他对象的情况下更改其状态,然后在适当的时候通知所有依赖于它的对象(观察者)。这种解耦的设计方式使得代码更加灵活,易于扩展和维护。
我有一个朋友张三,他总是关心天气情况,每天会看天气预报,在这个过程中,天气预报(被观察者)和张三(观察者)之间就会存在一种依赖关系。当天气预报发生变化时,张三需要得到通知并及时更新自己的信息。
定义角色:
- 被观察者(Subject):天气预报。它包含了当前的天气状况以及未来一段时间内的天气预报信息。
- 观察者(Observer):张三。他是一个依赖于天气预报信息的用户。
建立依赖关系:
- 张三订阅了天气预报服务,这样当他打开电视或查看手机时,就能接收到最新的天气预报信息。
事件通知机制:
- 天气预报服务会在天气状况发生变化时,或者新的预报信息生成时,触发通知机制。这个机制负责将最新的天气信息发送给所有订阅了服务的用户,包括张三。
更新策略:
- 张三在接收到天气预报信息后,会根据信息的内容更新自己的认知,比如决定是否要带伞、穿什么衣服等。
动态加入和退出:
- 如果张三决定不再订阅天气预报服务,他可以随时取消订阅。同样,如果张三从一个城市搬到另一个城市,他可以订阅新的城市的天气预报服务。
技术实现
首先,我们定义一个Subject接口和一个Observer接口:
// 被观察者
public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();
}// 观察者
public interface Observer {void update(String message);
}
然后,我们创建一个WeatherForecast类作为被观察者,实现Subject接口:
import java.util.ArrayList;
import java.util.List;public class WeatherForecast implements Subject {private List<Observer> observers = new ArrayList<>();private String message;public void setMessage(String message) {this.message = message;notifyObservers();}@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(message);}}
}
接下来,我们创建一个WeatherWatcher类作为观察者,实现Observer接口:
public class WeatherWatcher implements Observer {private String name;public WeatherWatcher(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " received weather forecast: " + message);}
}
最后,我们在主函数中创建一个WeatherForecast对象和两个WeatherWatcher对象,并让它们订阅天气预报:
public static void main(String[] args) {WeatherForecast weatherForecast = new WeatherForecast();WeatherWatcher watcher1 = new WeatherWatcher("张三");WeatherWatcher watcher2 = new WeatherWatcher("李四");weatherForecast.registerObserver(watcher1);weatherForecast.registerObserver(watcher2);weatherForecast.setMessage("今天天气晴朗,温度适中。");weatherForecast.setMessage("明天将会有大雨,请携带雨具。");
}
运行这个程序,你会看到张三和李四都收到了天气预报的通知。

Spring 实现
定义事件类:首先,我们需要定义一个事件类,它将携带被观察者状态变化的信息。
package com.neo.design.observer;import org.springframework.context.ApplicationEvent;public class WeatherEvent extends ApplicationEvent {private String weatherInfo;public WeatherEvent(Object source, String weatherInfo) {super(source);this.weatherInfo = weatherInfo;}public String getWeatherInfo() {return weatherInfo;}
}
- 创建事件发布者:接下来,我们创建一个事件发布者,它将负责发布天气变更事件。在这个例子中,我们将使用Spring的
ApplicationEventPublisher来发布事件。
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;@Component
public class WeatherEventPublisher {private final ApplicationEventPublisher publisher;public WeatherEventPublisher(ApplicationEventPublisher publisher) {this.publisher = publisher;}public void publishWeatherChangeEvent(String message) {publisher.publishEvent(new WeatherChangeEvent(message));}
}
创建事件监听器:然后,我们创建一个事件监听器,它将实现ApplicationListener接口,并重写onApplicationEvent方法。在这个方法中,我们将处理天气变更事件,并通知相关的观察者。
package com.neo.design.observer;import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;@Component
public class WeatherEventPublisher {private final ApplicationEventPublisher publisher;public WeatherEventPublisher(ApplicationEventPublisher publisher) {this.publisher = publisher;}public void publishWeatherChangeEvent(String message) {publisher.publishEvent(new WeatherChangeEvent(message));}
}
创建用户服务:我们还需要创建一个用户服务,它将负责管理用户的订阅信息,并在接收到天气变更事件时通知用户。
package com.neo.design.observer;import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
public class UserService {private final List<String> subscribers = new ArrayList<>();public void subscribe(String subscriber) {subscribers.add(subscriber);}public void notifySubscribers(String message) {for (String subscriber : subscribers) {System.out.println(subscriber + " received weather forecast: " + message);}}
}
创建控制器:最后,我们创建一个控制器,它将接收用户订阅请求和天气变更请求,并调用相应的服务来处理这些请求。
package com.neo.design.observer;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 WeatherForecastController {@Autowiredprivate UserService userService;@Autowiredprivate WeatherEventPublisher publisher;@PostMapping("/subscribe")public String subscribe(@RequestParam("subscriber") String subscriber) {userService.subscribe(subscriber);return "Subscriber added!";}@PostMapping("/update-weather")public String updateWeather(@RequestParam("message") String message) {publisher.publishWeatherChangeEvent(message);return "Weather updated!";}
}
通过以上设计,我们利用Spring Boot的事件机制和依赖注入特性实现了一个高效的观察者模式。
验证
新增一名观察者

设定一个被观察者所关注的消息。

执行功能,返回测试结果如下

总结
观察者模式(Observer Pattern)在软件工程设计中扮演着重要角色,观察者模式实现了发布者(主题)和订阅者(观察者)之间的松散耦合。发布者无需知道具体的订阅者是谁,只需要维护一个订阅者列表,并在状态变化时通知它们。这种解耦使得系统更具灵活性和可扩展性。通过观察者模式,添加或移除订阅者非常容易,不需要修改发布者的代码。只需实现观察者接口并注册或取消注册即可。这使得系统在需求变化或扩展时更易于维护。它适用于各种需要实时更新和异步处理的场景,提升了系统的响应能力和用户体验,是设计模式中一个非常实用且常用的模式。
相关文章:
观察者模式的思考
观察者模式由来 观察者模式(Observer Pattern)是一种行为型设计模式,它的起源可以追溯到20世纪90年代初,由设计模式四人帮(Erich Gamma, Richard Helm, Ralph Johnson 和 John Vlissides)在其著作《设计模…...
ORACLE SELECT INTO 赋值为空,抛出 NO DATA FOUND 异常
例子: DECLARE ORDER_NUM VARCHAR2(20); BEGIN SELECT S.ORDER_NUM INTO ORDER_NUM FROM SALES_ORDER S WHERE S.ID122344; DBMS_OUTPUT.PUT_LINE(单号: || ORDER_NUM); END; 在查询结果为空的情况下,以上代码会报错:未找到任何数据 解决方…...
GPT提示词
参考 提示词大全: GPT提示词大全(中英文双语)持续更新 提示词.com...
Redis协议详解及其异步应用
目录 一、Redis Pipeline(管道)概述优点使用场景工作原理Pipeline 的基本操作步骤C 示例(使用 [hiredis](https://github.com/redis/hiredis) 库) 二、Redis 事务概述事务的前提事务特征(ACID 分析)WATCH 命…...
LeetCode213:打家劫舍II
题目链接:213. 打家劫舍 II - 力扣(LeetCode) 代码如下 class Solution { public:int rob(vector<int>& nums) {if(nums.size() 0) return 0;if(nums.size() 1) return nums[0];if(nums.size() 2) return max(nums[0…...
linux一二三章那些是重点呢
第一章 静态库动态库的区别 什么是库 库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,它提供给使用者一些可以直接 拿来用的变量、函数或类。 如何制作 静态动态库 静态库: GCC 进行链接时,会把静态库中代码打…...
C语言中的程序入口:超越main函数的探索
在C语言中,尽管main函数是标准程序的默认入口点,但借助编译器特性和链接器选项,我们可以指定其他函数作为程序的入口。GCC编译器通过-e选项,允许我们将任何符合签名的函数作为程序的入口。这一特性可以用于特定的实验需求、特定系…...
《面试之MQ篇》
《面试之MQ篇》 1. 为什么要使用MQ 首先,面试官问的第一个问题或者说是逼问的一个问题:“为什么要使用MQ”其实面试官问这个问题就是想考察你MQ的特性,这个时候呢,我们必须要答出三点:解耦、异步、削峰。 1. 解耦 1. 传统系统…...
Git 分支操作-开发规范
一、背景 在实际开发中,一般在主分支的基础上单独创建一个新的分支进行开发,最后合并到master分支,而不是直接在master分支进行开发。 二、新建分支 1、初始状态,local为本地分支,remote为远程分支 2、单击 “Remot…...
JSONArray根据指定字段去重
JSONArray dataList new JSONArray();这儿省略dataList 加数据的过程 dataList new JSONArray(dataList.stream().distinct().collect(Collectors.toList())); Set<String> timestamps new HashSet<>();根据时间字段去重 dataList dataList.stream().map(obj -…...
线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?
在 Java 中,线程的生命周期管理通过不同的状态来跟踪。一个线程在其生命周期中可以处于多种状态,不同的状态之间会通过特定的事件发生转变。以下是 Java 线程的几种状态及其之间的转移方式: 1. 线程的状态 1.1 NEW(新建状态&…...
自注意力机制self-attention中的KV 缓存
在自注意力机制中,KV 缓存(Key-Value Caching)主要用于加速模型在推理阶段的计算,尤其是在处理长序列或者生成任务(如文本生成)时,这种缓存机制可以显著提高效率。 1. KV 缓存的背景 在 Trans…...
前端库--nanoid(轻量级的uuid)
文章目录 定义:生成方式:现实使用:NanoID 只有 108 个字节那么大NanoID更安全NanoID它既快速又紧凑 使用步骤1.安装nanoid包2.引入使用3.使用4.自定义字母 定义: UUID 是 通用唯一识别码(Universally Unique Identifierÿ…...
计算机基础-什么是网络端口?
网络端口可以想象成一个大型公寓楼的邮箱。每个公寓楼(这里指的是一个计算机或服务器)有很多个邮箱(即网络端口),每个邮箱都有一个独一无二的编号(端口号)。当一封信(网络数据包&…...
力扣动态规划基础版(斐波那契类型)
70. 爬楼梯https://leetcode.cn/problems/climbing-stairs/ 70.爬楼梯 方法一 动态规划 考虑转移方程和边界条件: f(x) f(x -1) f(x - 2);f(1) 1;f&…...
Java重修笔记 InetAddress 类和 Socket 类
InetAddress 类相关方法 1. 获取本机 InetAddress 对象:getLocalHost public static InetAddress getLocalHost() throws UnknownHostException 返回值:本地主机的名字和地址 异常:UnknownHostException - 如果本地主机名无法解析成地址 2…...
秋招突击——8/6——万得数据面试总结
文章目录 引言正文面经整理一1、讲一下java的多态,重载,重写的概念,区别2、说一下Java的数组,链表的结构,优缺点3、创建java线程的方式有哪些,具体说说4、创建线程池呢、每个参数的意义5、通过那几种方式保…...
STM32定时器
目录 STM32定时器概述 STM32基本定时器 基本定时器的功能 STM32基本定时器的寄存器 STM32通用定时器 STM32定时器HAL库函数 STM32定时器概述 从本质上讲定时器就是“数字电路”课程中学过的计数器(Counter),它像“闹钟”一样忠实地为处…...
第七课 Vue中的v-for遍历指令
Vue中的v-for遍历指令 v-for用于对象遍历(数组/JSON),渲染数据列表 基础示例: <div id"app"><ul><li v-for"val in arr">{{val}}</li></ul></div><script>new V…...
【NTN 卫星通信】卫星通信的专利
1 概述 好久没有看书了,最近买了本讲低轨卫星专利的书,也可以说是一个分析报告。推荐给喜欢的朋友。 2 书籍截图 图1 封面 图2 波音低轨卫星专利演进 图3 低轨卫星关键技术专利发展阶段 图4 第一页 3 参考文献 产业专利分析报告–低轨卫星通信技术...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...
离线语音识别方案分析
随着人工智能技术的不断发展,语音识别技术也得到了广泛的应用,从智能家居到车载系统,语音识别正在改变我们与设备的交互方式。尤其是离线语音识别,由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力,广…...
Python常用模块:time、os、shutil与flask初探
一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...
