当前位置: 首页 > news >正文

设计模式(二十)行为型:观察者模式详解

设计模式(二十)行为型:观察者模式详解

观察者模式(Observer Pattern)是 GoF 23 种设计模式中最具影响力的行为型模式之一,其核心价值在于定义对象间的一对多依赖关系,当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会自动收到通知并更新。它实现了发布-订阅(Publish-Subscribe)机制,是事件驱动架构、响应式编程、GUI 组件联动、消息系统、数据绑定、实时通知等现代软件系统的基础。观察者模式通过解耦被观察者与观察者,实现了高度的灵活性和可扩展性,是构建松耦合、高内聚、可维护系统的基石。

一、详细介绍

观察者模式解决的是“当一个对象的状态变化需要通知多个其他对象,且这些对象的具体类型和数量在编译时未知或可能动态变化”的问题。在传统设计中,被观察者可能需要持有所有观察者的引用,并在状态变化时逐一调用其更新方法,这导致:

  • 紧耦合:被观察者依赖于具体的观察者类。
  • 缺乏灵活性:新增观察者需要修改被观察者的代码。
  • 难以复用:被观察者无法独立于特定的观察者集合使用。
  • 违反开闭原则:对扩展开放,对修改关闭。

观察者模式的核心思想是:引入抽象接口,将“通知”与“更新”行为抽象化,使被观察者仅依赖于抽象的观察者接口,而非具体实现

该模式包含以下核心角色:

  • Subject(被观察者/主题):也称为“可观察对象”(Observable)。它维护一个观察者列表,提供注册(attach)、注销(detach)观察者的方法,以及通知(notify)所有观察者的方法。当自身状态改变时,调用 notify
  • Observer(观察者):定义一个更新接口(update),所有具体观察者必须实现该接口。当收到通知时,执行相应的更新逻辑。
  • ConcreteSubject(具体被观察者):继承或实现 Subject,包含具体的状态数据。当状态改变时,调用父类的 notify 方法。
  • ConcreteObserver(具体观察者):实现 Observer 接口,持有对 ConcreteSubject 的引用(可选,用于获取最新状态),并在 update 方法中实现具体的响应逻辑。

观察者模式的关键优势:

  • 解耦:被观察者与观察者之间仅通过抽象接口通信,降低耦合度。
  • 支持广播通信:一个被观察者可通知多个观察者。
  • 动态注册/注销:观察者可在运行时动态加入或退出通知列表。
  • 支持多种响应:不同观察者可对同一事件做出不同反应。
  • 符合开闭原则:新增观察者无需修改被观察者代码。

与“中介者模式”相比,观察者是一对多的单向通知,中介者是多对多的双向协调;与“命令模式”相比,命令封装请求,观察者处理状态变化;与“备忘录模式”相比,备忘录关注状态保存与恢复,观察者关注状态变化的通知

观察者模式适用于:

  • GUI 组件联动(如滑块改变影响标签)。
  • 模型-视图-控制器(MVC)架构中的模型与视图同步。
  • 消息队列、事件总线、发布-订阅系统。
  • 实时数据监控与告警。
  • 缓存失效通知。

二、观察者模式的UML表示

以下是观察者模式的标准 UML 类图:

implements
implements
implements
has many
observes
observes
«interface»
Subject
+attach(observer: Observer)
+detach(observer: Observer)
+notify()
ConcreteSubject
-state: String
-observers: List<Observer>
+getState()
+setState(state: String)
+attach(observer: Observer)
+detach(observer: Observer)
+notify()
«interface»
Observer
+update()
ConcreteObserverA
-name: String
-subject: ConcreteSubject
+update()
ConcreteObserverB
-name: String
-subject: ConcreteSubject
+update()

图解说明

  • Subject 定义注册、注销、通知的抽象接口。
  • ConcreteSubject 维护观察者列表,状态改变时调用 notify
  • Observer 定义 update 接口。
  • ConcreteObserver 实现 update,并在被通知时执行具体逻辑。
  • 观察者通常持有对被观察者的引用,以便在 update 中获取最新状态。

三、一个简单的Java程序实例及其UML图

以下是一个天气数据发布系统的示例,气象站(被观察者)发布天气数据,多个显示设备(观察者)接收并显示。

Java 程序实例
import java.util.ArrayList;
import java.util.List;// 观察者接口
interface Observer {void update();
}// 被观察者接口
interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers();
}// 具体被观察者:天气数据
class WeatherData implements Subject {private float temperature;private float humidity;private float pressure;private List<Observer> observers;public WeatherData() {this.observers = new ArrayList<>();}// 状态改变时调用此方法(由外部系统或传感器触发)public void setMeasurements(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;System.out.println("\n🌤️  天气数据更新: 温度=" + temperature + "°C, 湿度=" + humidity + "%, 气压=" + pressure + "hPa");notifyObservers(); // 通知所有观察者}// 获取最新状态public float getTemperature() { return temperature; }public float getHumidity() { return humidity; }public float getPressure() { return pressure; }@Overridepublic void attach(Observer observer) {observers.add(observer);System.out.println("✅ 观察者 [" + observer.getClass().getSimpleName() + "] 已注册");}@Overridepublic void detach(Observer observer) {observers.remove(observer);System.out.println("❌ 观察者 [" + observer.getClass().getSimpleName() + "] 已注销");}@Overridepublic void notifyObservers() {System.out.println("📢 正在通知 " + observers.size() + " 个观察者...");for (Observer observer : observers) {observer.update();}}
}// 具体观察者:当前条件显示
class CurrentConditionsDisplay implements Observer {private float temperature;private float humidity;private WeatherData weatherData; // 持有被观察者引用public CurrentConditionsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.attach(this); // 注册自己}@Overridepublic void update() {this.temperature = weatherData.getTemperature();this.humidity = weatherData.getHumidity();display();}public void display() {System.out.println("  📊 当前条件: 温度 " + temperature + "°C, 湿度 " + humidity + "%");}// 可提供注销方法public void remove() {weatherData.detach(this);}
}// 具体观察者:统计显示
class StatisticsDisplay implements Observer {private float maxTemp = 0.0f;private float minTemp = 200.0f;private float tempSum = 0.0f;private int numReadings = 0;private WeatherData weatherData;public StatisticsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.attach(this);}@Overridepublic void update() {float temp = weatherData.getTemperature();tempSum += temp;numReadings++;if (temp > maxTemp) maxTemp = temp;if (temp < minTemp) minTemp = temp;display();}public void display() {System.out.println("  📈 统计信息: 平均=" + (tempSum / numReadings) + "°C, 最高=" + maxTemp + "°C, 最低=" + minTemp + "°C");}
}// 具体观察者:预测显示
class ForecastDisplay implements Observer {private float currentPressure = 29.92f;private float lastPressure;private WeatherData weatherData;public ForecastDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.attach(this);}@Overridepublic void update() {lastPressure = currentPressure;currentPressure = weatherData.getPressure();display();}public void display() {System.out.print("  🌦️  天气预报: ");if (currentPressure > lastPressure) {System.out.println("天气好转!");} else if (currentPressure == lastPressure) {System.out.println("天气稳定。");} else {System.out.println("天气转坏!");}}
}// 客户端使用示例
public class ObserverPatternDemo {public static void main(String[] args) {System.out.println("🌤️  天气观测系统 - 观察者模式示例\n");// 创建被观察者WeatherData weatherData = new WeatherData();// 创建观察者(它们在构造时自动注册)CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);// 模拟数据更新System.out.println("\n--- 第一次数据更新 ---");weatherData.setMeasurements(80, 65, 30.4f);System.out.println("\n--- 第二次数据更新 ---");weatherData.setMeasurements(82, 70, 29.2f);System.out.println("\n--- 第三次数据更新 ---");weatherData.setMeasurements(78, 90, 29.2f);// 演示注销观察者System.out.println("\n--- 注销统计显示 ---");statisticsDisplay.remove();System.out.println("\n--- 第四次数据更新(统计显示不再更新)---");weatherData.setMeasurements(62, 90, 28.1f);}
}
实例对应的UML图(简化版)
has many
observes
observes
observes
implements
implements
implements
WeatherData
-temperature: float
-humidity: float
-pressure: float
-observers: List<Observer>
+setMeasurements(temp, hum, pres)
+attach(observer: Observer)
+detach(observer: Observer)
+notifyObservers()
CurrentConditionsDisplay
-temperature: float
-humidity: float
-weatherData: WeatherData
+update()
+display()
+remove()
StatisticsDisplay
-maxTemp: float
-minTemp: float
-tempSum: float
-numReadings: int
-weatherData: WeatherData
+update()
+display()
ForecastDisplay
-currentPressure: float
-lastPressure: float
-weatherData: WeatherData
+update()
+display()
«interface»
Observer
+update()

运行说明

  • WeatherData 是被观察者,维护状态和观察者列表。
  • 三个具体观察者(CurrentConditionsDisplay, StatisticsDisplay, ForecastDisplay)在构造时注册到 WeatherData
  • setMeasurements 被调用时,WeatherData 调用 notifyObservers,遍历所有观察者并调用其 update 方法。
  • 每个观察者在 update 中获取最新数据并更新自身显示。
  • 可动态注销观察者(如 statisticsDisplay.remove()),之后不再接收通知。

四、总结

特性说明
核心目的实现对象间一对多的依赖,自动通知状态变化
实现机制被观察者维护观察者列表,状态变时广播通知
优点解耦、支持广播、动态注册、符合开闭原则
缺点可能导致意外更新(级联通知)、观察者过多影响性能、内存泄漏(未注销)
适用场景GUI 事件处理、MVC 架构、消息系统、实时通知、数据绑定
不适用场景依赖关系简单、观察者数量极少、性能极度敏感

观察者模式使用建议

  • 确保观察者能及时注销,避免内存泄漏。
  • 考虑通知顺序和性能,大量观察者时可异步通知。
  • Java 内置 java.util.Observablejava.util.Observer,但已标记为过时,推荐自定义或使用现代框架。
  • 现代框架如 Spring 的 ApplicationEvent、JavaFX 的 Property 绑定、RxJava 的 Observable 都是其高级实现。

架构师洞见:
观察者模式是“事件驱动”与“响应式”架构的基石。在现代系统中,其思想已演变为反应式编程(Reactive Programming)事件溯源(Event Sourcing)消息中间件(Kafka, RabbitMQ)前端框架(React, Vue)的响应式系统 的核心。例如,Kafka 的 Topic-Consumer 模型是分布式观察者;前端框架的响应式数据绑定让 UI 自动响应数据变化;微服务中的事件总线(Event Bus)实现服务间解耦通信;在 AI 系统中,模型训练进度可作为事件通知监控系统。

未来趋势是:观察者将与流处理(Stream Processing) 深度融合,支持复杂事件处理(CEP);在边缘计算中,设备状态变化触发本地观察者;在量子网络中,量子态的纠缠可视为一种超距观察者机制;在元宇宙中,用户动作作为事件被多个虚拟对象观察。

掌握观察者模式,是设计松耦合、高响应性、可扩展系统的必经之路。作为架构师,应在设计任何需要“状态变化通知”或“组件联动”的场景时,优先考虑观察者模式。它不仅是模式,更是系统生命力的体现——它让系统从“被动调用”走向“主动响应”,从“静态结构”走向“动态演化”,构建出能够感知变化、自动适应、持续进化的智能软件生态。

http://www.dtcms.com/a/302037.html

相关文章:

  • 常用设计模式系列(十五)—解释器模式
  • 修改CentOS的SSH登录端口(22端口)
  • python任意模块间采用全局字典来实现借用其他类对象的方法函数来完成任务或数据通信的功能
  • STM32入门之DMA直接存储器存取
  • 第4章唯一ID生成器——4.3 基于时间戳的趋势递增的唯一ID
  • Java 排序
  • LeetCode 刷题【18. 四数之和】
  • Flutter实现Android原生相机拍照
  • 如何在技术世界中保持清醒和高效
  • iphone手机使用charles代理,chls.pro/ssl 后回车 提示浏览器打不开该网页
  • NI Ettus USRP X440 软件无线电
  • 免费 SSL 证书申请简明教程,让网站实现 HTTPS 访问
  • PyTorch 使用指南
  • 基于Spring Boot的审计日志自动化解决方案,结合SpEL表达式和AOP技术,实现操作轨迹自动记录,并满足GDPR合规要求
  • <七> CentOS 8 安装最新版本Docker
  • 从零开始的云计算生活——第三十七天,跬步千里,ansible之playbook
  • LWGJL教程(8)——基础知识
  • JavaScript手录-排序算法篇
  • UNet改进(26):UNet结合分层注意力机制的图像分割深度解析
  • socketpair函数详解
  • CHI - Transaction介绍 - 其他类型介绍
  • 图论(BFS)构造邻接表(运用队列实现搜索)
  • Java面试深度剖析:从JVM到云原生的技术演进
  • 10.若依的自定义注解 Log
  • 发布“悟能”具身智能平台,商汤让机器人像人一样和现实世界交互
  • GitLab 18.2 发布几十项与 DevSecOps 有关的功能,可升级体验【一】
  • RAGFlow系列(03):把知识库通过API方式共享给Dify等外部平台使用
  • WPS 将一个PPT里面的图片和文字导入到另一个PPT中
  • CSP-J 2022_第三题逻辑表达式
  • 面试官:详细说说Kafka rebalance 的策略以及具体过程