【设计模式】【行为型模式】观察者模式(Observer)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
📫 欢迎+V: flzjcsg2,我们共同讨论Java深渊的奥秘
🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

  • 一、入门
    • 什么是观察者模式?
    • 为什么要观察者模式?
    • 怎么实现观察者模式?
  • 二、观察者模式在源码中运用
    • Java 中的 java.util.Observer 和 java.util.Observable
      • Observer和Observable的使用
      • Observer和Observable的源码实现
    • Spring 框架中的事件机制
      • Spring事件机制的使用
      • Spring的事件机质的源码实现
  • 三、总结
    • 观察者模式的优点
    • 观察者模式的缺点
    • 观察者模式的适用场景
  • 参考

一、入门

什么是观察者模式?

观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并自动更新。

为什么要观察者模式?

假设我们正在开发一个天气预报系统,其中:

  • WeatherStation(气象站):负责收集天气数据(如温度、湿度等)。
  • Display(显示设备):负责显示天气数据,比如手机App、电子屏等。

当气象站的数据更新时,所有显示设备都需要实时更新显示内容。
下面是没有观察者模式时的实现:

class WeatherStation {private float temperature;private float humidity;private PhoneDisplay phoneDisplay;private TVDisplay tvDisplay;public void setPhoneDisplay(PhoneDisplay phoneDisplay) {this.phoneDisplay = phoneDisplay;}public void setTVDisplay(TVDisplay tvDisplay) {this.tvDisplay = tvDisplay;}public void removePhoneDisplay() {phoneDisplay = null;}public void removeTVDisplay() {phoneDisplay = null;}public void setMeasurements(float temperature, float humidity) {this.temperature = temperature;this.humidity = humidity;// 手动调用显示设备的更新方法if (phoneDisplay != null) {phoneDisplay.update(temperature, humidity);}if (tvDisplay != null) {tvDisplay.update(temperature, humidity);}}
}

紧耦合
如果没有观察者模式,气象站需要直接知道所有显示设备的存在,并手动调用它们的更新方法。例如:
问题:

  • 气象站和显示设备之间是紧耦合的,气象站需要知道所有显示设备的具体实现。
  • 如果新增一个显示设备(比如智能手表),需要修改气象站的代码,违反了开闭原则(对扩展开放,对修改关闭)。

难以动态管理依赖
如果显示设备需要动态添加或移除(比如用户关闭了某个显示设备),气象站需要手动管理这些设备的引用。

扩展性差
如果未来需要支持更多类型的观察者(比如日志记录器、报警系统等),气象站的代码会变得越来越臃肿,难以维护。

怎么实现观察者模式?

在观察者模式中有如下角色:

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

【案例】天气站 - 改
在这里插入图片描述
Observer观察者: Observer接口

interface Observer {void update(float temperature, float humidity);
}

Subject主题: subject接口

interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();
}

实现具体主题(气象站): WeatherStation

class WeatherStation implements Subject {private List<Observer> observers = new ArrayList<>();private float temperature;private float humidity;@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(temperature, humidity);}}public void setMeasurements(float temperature, float humidity) {this.temperature = temperature;this.humidity = humidity;notifyObservers(); // 通知所有观察者}
}

实现具体观察者(显示设备): PhoneDisplay类和TVDisplay

class PhoneDisplay implements Observer {@Overridepublic void update(float temperature, float humidity) {System.out.println("手机显示:温度 = " + temperature + ",湿度 = " + humidity);}
}class TVDisplay implements Observer {@Overridepublic void update(float temperature, float humidity) {System.out.println("电视显示:温度 = " + temperature + ",湿度 = " + humidity);}
}

测试类

public class WeatherApp {public static void main(String[] args) {WeatherStation weatherStation = new WeatherStation();Observer phoneDisplay = new PhoneDisplay();Observer tvDisplay = new TVDisplay();weatherStation.registerObserver(phoneDisplay);weatherStation.registerObserver(tvDisplay);// 更新天气数据weatherStation.setMeasurements(25.5f, 60.0f);// 移除一个观察者weatherStation.removeObserver(tvDisplay);// 再次更新天气数据weatherStation.setMeasurements(26.0f, 58.0f);}
}

运行结果

手机显示:温度 = 25.5,湿度 = 60.0
电视显示:温度 = 25.5,湿度 = 60.0
手机显示:温度 = 26.0,湿度 = 58.0

二、观察者模式在源码中运用

Java 中的 java.util.Observer 和 java.util.Observable

Java 标准库中提供了观察者模式的实现,分别是 Observer 接口和 Observable 类。

  • Observable 是被观察者的基类,内部维护了一个观察者列表,并提供了 addObserver、deleteObserver 和 notifyObservers 方法。
  • Observer 是观察者接口,定义了 update 方法,用于接收通知。

Observer和Observable的使用

被观察者(具体主题):WeatherData

// 被观察者(主题)
class WeatherData extends Observable {private float temperature;private float humidity;public void setMeasurements(float temperature, float humidity) {this.temperature = temperature;this.humidity = humidity;setChanged(); // 标记状态已改变notifyObservers(); // 通知观察者}public float getTemperature() {return temperature;}public float getHumidity() {return humidity;}
}

观察者(具体观察者): Display

// 观察者
class Display implements Observer {@Overridepublic void update(Observable o, Object arg) {if (o instanceof WeatherData) {WeatherData weatherData = (WeatherData) o;float temperature = weatherData.getTemperature();float humidity = weatherData.getHumidity();System.out.println("当前温度: " + temperature + ",湿度: " + humidity);}}
}

测试

public class ObserverPatternDemo {public static void main(String[] args) {WeatherData weatherData = new WeatherData();Display display = new Display();weatherData.addObserver(display); // 注册观察者weatherData.setMeasurements(25.5f, 60.0f); // 更新数据并通知观察者}
}

输出结果

当前温度: 25.5,湿度: 60.0

Observer和Observable的源码实现

观察者:Observer类,入参Observable o:被观察的对象(主题)和 Object arg:传递给观察者的额外参数(可选)。

public interface Observer {void update(Observable o, Object arg);
}

主题Observable类。
我们可以看到Vector<Observer>存储观察者列表。并且因为加了synchronized关键字,这些方法都是线程安全的。notifyObservers方法会遍历观察者列表,并调用每个观察者的update方法。

public class Observable {// 标记对象是否已改变private boolean changed = false;// 观察者列表(使用 Vector 保证线程安全)private Vector<Observer> obs;public Observable() {obs = new Vector<>();}// 添加观察者public synchronized void addObserver(Observer o) {if (o == null)throw new NullPointerException();if (!obs.contains(o)) {obs.addElement(o);}}// 删除观察者public synchronized void deleteObserver(Observer o) {obs.removeElement(o);}// 通知所有观察者(无参数)public void notifyObservers() {notifyObservers(null);}// 通知所有观察者(带参数)public void notifyObservers(Object arg) {Observer[] arrLocal;// 同步块,确保线程安全synchronized (this) {if (!changed) // 如果没有变化,直接返回return;arrLocal = obs.toArray(new Observer[obs.size()]);clearChanged(); // 重置变化标志}// 遍历观察者列表,调用 update 方法for (Observer observer : arrLocal) {observer.update(this, arg);}}// 删除所有观察者public synchronized void deleteObservers() {obs.removeAllElements();}// 标记对象已改变protected synchronized void setChanged() {changed = true;}// 重置变化标志protected synchronized void clearChanged() {changed = false;}// 检查对象是否已改变public synchronized boolean hasChanged() {return changed;}// 返回观察者数量public synchronized int countObservers() {return obs.size();}
}

Spring 框架中的事件机制

Spring 框架中的事件机制是基于观察者模式实现的。它允许开发者定义自定义事件,并通过监听器(观察者)来处理这些事件。

Spring事件机制的使用

自定义事件

class CustomEvent extends ApplicationEvent {private String message;public CustomEvent(Object source, String message) {super(source);this.message = message;}public String getMessage() {return message;}
}

事件监听器(观察者)

@Component
class CustomEventListener implements ApplicationListener<CustomEvent> {@Overridepublic void onApplicationEvent(CustomEvent event) {System.out.println("收到事件: " + event.getMessage());}
}

事件发布者

@Component
class CustomEventPublisher {private final AnnotationConfigApplicationContext context;public CustomEventPublisher(AnnotationConfigApplicationContext context) {this.context = context;}public void publishEvent(String message) {context.publishEvent(new CustomEvent(this, message));}
}

配置类

@Configuration
@ComponentScan
class AppConfig {}

测试类

public class SpringEventDemo {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);CustomEventPublisher publisher = context.getBean(CustomEventPublisher.class);publisher.publishEvent("Hello, Spring Event!");context.close();}
}

输出内容

收到事件: Hello, Spring Event!

Spring的事件机质的源码实现

Spring 事件机制的核心组件包括:

  • ApplicationEvent:事件的基类,所有自定义事件都需要继承它。对应观察者模式中的“事件”。
  • ApplicationListener:观察者接口,定义了处理事件的方法。对应观察者模式中的“观察者”。
  • ApplicationEventPublisher:事件发布者接口,用于发布事件。对应观察者模式中的“主题”。
  • ApplicationEventMulticaster:事件广播器,负责将事件分发给所有监听器。类似于观察者模式中的“通知机制”。

ApplicationEventApplicationEvent 是所有事件的基类,它继承自 java.util.EventObject

public abstract class ApplicationEvent extends EventObject {private final long timestamp;            // timestamp: 事件发生的时间戳。public ApplicationEvent(Object source) { // source:事件源,通常是发布事件的对象。super(source);this.timestamp = System.currentTimeMillis();}public final long getTimestamp() {return this.timestamp;}
}

ApplicationListener接口: ApplicationListener是观察者接口,定义了处理事件的方法。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);  // 当事件发生时,会调用此方法。
}

ApplicationEventPublisher接口: 是事件发布者接口,用于发布事件。

@FunctionalInterface
public interface ApplicationEventPublisher {default void publishEvent(ApplicationEvent event) {publishEvent((Object) event);}void publishEvent(Object event);
}

ApplicationEventMulticaster接口:是事件广播器接口,负责将事件分发给所有监听器。

public interface ApplicationEventMulticaster {void addApplicationListener(ApplicationListener<?> listener);void addApplicationListenerBean(String listenerBeanName);void removeApplicationListener(ApplicationListener<?> listener);void removeApplicationListenerBean(String listenerBeanName);void removeAllListeners();void multicastEvent(ApplicationEvent event); void multicastEvent(ApplicationEvent event, ResolvableType eventType);
}

SimpleApplicationEventMulticasterSimpleApplicationEventMulticasterApplicationEventMulticaster的默认实现类。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {// 遍历所有监听器,并调用 onApplicationEvent 方法。@Overridepublic void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {Executor executor = getTaskExecutor();if (executor != null) {executor.execute(() -> invokeListener(listener, event));} else {invokeListener(listener, event);}}}// 实际调用监听器的 onApplicationEvent 方法。protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {try {listener.onApplicationEvent(event);} catch (ClassCastException ex) {// 处理类型转换异常}}
}

三、总结

观察者模式的优点

解耦:主题(Subject)和观察者(Observer)之间是松耦合的(主题不需要知道观察者的具体实现,只需要知道观察者接口,观察者也不需要知道主题的具体实现,只需要实现观察者接口)
动态管理依赖:观察者可以动态注册和注销,而不需要修改主题的代码。支持运行时动态添加或移除观察者,灵活性高。
符合开闭原则:可以轻松添加新的观察者,而不需要修改主题的代码。主题的代码不需要因为观察者的变化而修改。
广播通信:主题可以一次性通知所有观察者,适合一对多的通信场景。观察者可以根据需要选择是否响应通知。
职责分离:主题负责维护状态和通知观察者。观察者负责处理状态变化的逻辑。职责分离使得代码更加清晰和可维护。

观察者模式的缺点

  • 性能问题
    • 如果观察者数量非常多,通知所有观察者可能会消耗大量时间。
    • 如果观察者的处理逻辑复杂,可能会导致性能瓶颈。
  • 内存泄漏
    • 如果观察者没有正确注销,可能会导致观察者无法被垃圾回收,从而引发内存泄漏。
    • 特别是在长时间运行的应用中,需要特别注意观察者的生命周期管理。
  • 调试困难
    • 由于观察者和主题是松耦合的,调试时可能难以追踪事件的传递路径。
    • 如果观察者的处理逻辑出现问题,可能不容易定位问题根源。
  • 事件顺序不确定
    • 观察者收到通知的顺序通常是不确定的,如果业务逻辑对顺序有要求,可能需要额外的处理。

观察者模式的适用场景

  • 事件驱动系统
    • 当一个对象的状态变化需要触发其他对象的操作时,可以使用观察者模式。
    • 例如:GUI 框架中的按钮点击事件、Spring 框架中的事件机制。
  • 一对多的依赖关系
    • 当一个对象的状态变化需要通知多个其他对象时,可以使用观察者模式。
    • 例如:气象站和多个显示设备的关系。
  • 跨系统的消息通知
    • 在分布式系统中,观察者模式可以用于实现消息的发布和订阅。
    • 例如:消息队列(MQ)中的发布-订阅模型。
  • 状态变化的广播
    • 当一个对象的状态变化需要广播给多个对象时,可以使用观察者模式。
    • 例如:游戏中的角色状态变化通知其他系统(如 UI、音效等)。
  • 解耦业务逻辑
    • 当需要将业务逻辑解耦为多个独立的模块时,可以使用观察者模式。
    • 例如:订单系统中的订单状态变化通知库存系统、物流系统等。

参考

黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili

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

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

相关文章

git 克隆指定 tag 的项目

git 克隆指定 tag 的项目 一、克隆指定tag的项目二、验证克隆结果 一、克隆指定tag的项目 以 tinyxml2项目 为例说明&#xff1a; git clone --branch V10.0.0 https://github.com/leethomason/tinyxml2.git解释&#xff1a; git clone&#xff1a;这是克隆一个远程仓库的命…

OSPF高级特性(3):安全特效

引言 OSPF的基础我们已经结束学习了&#xff0c;接下来我们继续学习OSPF的高级特性。为了方便大家阅读&#xff0c;我会将高级特性的几篇链接放在末尾&#xff0c;所有链接都是站内的&#xff0c;大家点击即可阅读&#xff1a; OSPF基础&#xff08;1&#xff09;&#xff1a;工…

把 DeepSeek1.5b 部署在显卡小于4G的电脑上

这里写自定义目录标题 介绍准备安装 Ollama查看CUDA需要版本安装CudaToolkit检查Cuda是否装好二、设置Ollama环境变量三、验证是否跑在GPU上ollama如何导入本地下载的模型安装及配置docker安装open-webui启动open-webui开始对话介绍 Deepseek1.5b能够运行在只用cpu和gpu内存小…

WebSocket与Socket.io的区别

文章目录 引言一、WebSocket&#xff1a;原生的实时通信协议&#xff08;一&#xff09;WebSocket 是什么&#xff08;二&#xff09;WebSocket 的工作原理&#xff08;三&#xff09;WebSocket 的使用方法&#xff08;四&#xff09;WebSocket 的优势&#xff08;五&#xff0…

STM32 裸机 C编程 vs micropython编程 vs linux python

以led点亮为例。 STM32 裸机 C编程需要设置时钟&#xff0c;管脚。 static void MX_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct {0};// GPIO端口时钟使能__HAL_RCC_GPIOA_CLK_ENABLE();// 配置PA5为推挽输出模式GPIO_InitStruct.Pin GPIO_PIN_5;GPIO_InitStruct.M…

AI语言模型的技术之争:DeepSeek与ChatGPT的架构与训练揭秘

云边有个稻草人-CSDN博客 目录 第一章&#xff1a;DeepSeek与ChatGPT的基础概述 1.1 DeepSeek简介 1.2 ChatGPT简介 第二章&#xff1a;模型架构对比 2.1 Transformer架构&#xff1a;核心相似性 2.2 模型规模与参数 第三章&#xff1a;训练方法与技术 3.1 预训练与微调…

【科技革命】颠覆性力量与社会伦理的再平衡

目录 2025年科技革命&#xff1a;颠覆性力量与社会伦理的再平衡目录技术突破全景图认知智能的范式转移量子霸权实现路径生物编程技术革命能源结构重构工程 产业生态链重构医疗健康新范式教育系统智能进化金融基础设施变革制造范式革命 科技伦理与文明演进 2025年科技革命&#…

稀土抑烟剂——为汽车火灾安全增添防线

一、稀土抑烟剂的基本概念 稀土抑烟剂是一类基于稀土元素&#xff08;如稀土氧化物和稀土金属化合物&#xff09;开发的高效阻燃材料。它可以显著提高汽车内饰材料的阻燃性能&#xff0c;减少火灾发生时有毒气体和烟雾的产生。稀土抑烟剂不仅能提升火灾时的安全性&#xff0c;…

名词解释:npm,cnpm,yarn,vite,vue,electron

1. npm (Node Package Manager) 读音: “N-P-M” 或者直接读作 “npm”。 npm 是 Node.js 的官方包管理器&#xff0c;用于安装、发布和管理 JavaScript 软件包。它允许开发者轻松地共享代码&#xff0c;并且可以通过命令行工具来管理依赖关系。通过 npm init 命令可以交互式…

ES 索引结构

ES 既不像 MySQL 这样有严格的 Schema&#xff0c;也不像 MongoDB 那样完全无 Schema&#xff0c;而是介于两者之间。 1️⃣ ES 的 Schema 模式 ES 默认是 Schema-less&#xff08;无模式&#xff09; 的&#xff0c;允许动态添加字段。 但 ES 也支持 Schema&#xff08;映射 …

硅基流动平台大模型 DeepSeek API 调用示例

硅基流动平台大模型 API 调用示例 硅基流动平台作为一个集成多种主流开源大模型的云服务平台&#xff0c;为用户提供了便捷的 API 调用方式&#xff0c;让用户无需自建硬件或进行复杂配置&#xff0c;即可轻松使用各种大模型。以下是详细的硅基流动平台大模型 API 调用示例&am…

vue项目 Axios创建拦截器

Axios 1. Axios 和 Ajax 简介2. Axios 和 Ajax 的区别3. 从 按钮 到 Axios请求后端接口的 大致顺序 1. Axios 和 Ajax 简介 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09; 不是一种技术&#xff0c;而是一个编程技术概念&#xff0c;核心是通过 XMLHttpReques…

CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测,光伏功率预测

CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测&#xff0c;光伏功率预测 代码下载&#xff1a;CNN-BiGRU卷积神经网络双向门控循环单元多变量多步预测&#xff0c;光伏功率预测 一、引言 1.1、研究背景及意义 随着全球能源危机和环境问题的日益严重&#xff0c;可再…

碳纤维复合材料制造的六西格玛管理实践:破解高端制造良率困局的实战密码

碳纤维复合材料制造的六西格玛管理实践&#xff1a;破解高端制造良率困局的实战密码 在全球碳中和与高端制造升级的双重驱动下&#xff0c;碳纤维复合材料行业正经历前爆发式增长。航空航天、新能源汽车、风电叶片等领域对碳纤维产品的性能稳定性提出近乎苛刻的要求&#xff0…

如何保证Redis和MySQL数据的一致性刨析

1、常见的缓存更新策略&#xff1a; 定义&#xff1a;主要用来进行redis和mysql的数据同步更新的一些策略 内存淘汰&#xff1a;等触发淘汰机制后&#xff0c;刚好淘汰到了用户查询的数据&#xff0c;此时是null&#xff0c;会进行查询数据库并写入到缓存中&#xff0c;此时…

基于 DeepSeek 的创新点及其在学术研究与论文发表中的应用

基于 DeepSeek 的创新点及其在学术研究与论文发表中的应用 随着人工智能技术的不断发展&#xff0c;DeepSeek 作为一款具有创新性的大型语言模型&#xff0c;为学术研究和论文发表带来了新的机遇。本文将详细介绍 DeepSeek 的创新点&#xff0c;并探讨如何利用这些创新点撰写和…

Golang GORM系列:GORM 高级查询教程

有效的数据检索是任何程序功能的基础。健壮的Go对象关系映射包&#xff08;称为GORM&#xff09;除了标准的CRUD操作之外&#xff0c;还提供了复杂的查询功能。这是学习如何使用GORM进行高级查询的综合资源。我们将涵盖WHERE条件、连接、关联、预加载相关数据&#xff0c;甚至涉…

协议-LVDS

是什么&#xff1f; LVDS 全称为 Low-Voltage Differential Signaling&#xff0c;低电压差分信号 低功耗、低误码率、低串扰和低辐射的差分信号&#xff0c;采用-350mV~350mV极底的电压摆幅高速差动传输数据&#xff0c;实现点对点或一点对多点的连接 由于电压幅度低&#xf…

dma_ddr 的编写 通过mig控制ddr3

此外还有别的模块 本模块是 其中一个 timescale 1ns/1ps module dma_ctrl (input wire ui_clk , //100MHZ 用户时钟input wire ui_rst_n ,//写fifo的写端口 input wire wf_wr_clk , //由数据产生模块的时…

数据中心网络监控

数据中心是全球协作的特定设备网络&#xff0c;用来在internet网络基础设施上传递、加速、展示、计算、存储数据信息。 对于任何利用IT基础设施的企业来说&#xff0c;数据中心都是运营的核心&#xff0c;它本质上为整个业务网络托管业务应用程序和存储空间。数据中心可以是任…