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

【观察者模式】设计模式系列: 实现与最佳实践案例分析

文章目录

  • 观察者模式深入解析:在Java中的实现与应用
    • 1. 引言
      • 1.1 观察者模式简介
      • 1.2 模式的重要性及其在现实世界的应用示例
      • 1.3 本文的目标和读者定位
    • 2. 观察者模式的基本概念
      • 2.1 定义与原理
      • 2.2 UML类图和时序图
      • 2.3 核心原则
      • 2.4 使用场景
    • 3. 观察者模式与其他模式的关系
      • 3.1 与工厂模式
      • 3.2 与策略模式
      • 3.3 与组合模式
    • 4. 观察者模式的实现
      • 4.1 Java内置支持
      • 4.2 自定义实现
      • 4.3 代码示例
    • 5. 高级话题
      • 5.1 异步观察者模式
      • 5.2 线程安全问题
      • 5.3 性能考量
    • 6. 案例研究
      • 6.1 股票报价系统
      • 6.2 天气预报系统
    • 7. 观察者模式的变体
      • 7.1 事件监听器模式
      • 7.2 发布-订阅模式
      • 7.3 其他相关模式
    • 8. 实战经验分享
      • 8.1 常见错误与陷阱
      • 8.2 调试技巧
      • 8.3 重构建议
    • 9. 总结与展望

观察者模式深入解析:在Java中的实现与应用


1. 引言

1.1 观察者模式简介

观察者模式是一种软件设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。这种模式属于行为型设计模式之一,主要应用于实现发布-订阅机制。

1.2 模式的重要性及其在现实世界的应用示例

观察者模式的重要性在于它能够简化对象之间的交互逻辑,使得各个对象更加独立。在现实世界中,这种模式的应用十分广泛:

  • 新闻系统:当新闻发布时,所有订阅该新闻的用户会收到更新通知。
  • 股市行情:投资者关注特定股票的价格变动,价格变动时会及时通知到所有关注者。
  • 操作系统通知中心:应用程序可以注册成为某个事件的通知接收者,例如电池电量低时的通知。

1.3 本文的目标和读者定位

本文旨在详细介绍观察者模式的概念、原理以及在Java中的实现方法。此外,还将探讨观察者模式与其他几种设计模式的关系,以及如何在实际项目中应用这些模式。本教程适合已经了解基本面向对象编程概念的Java开发者阅读。


2. 观察者模式的基本概念

2.1 定义与原理

1. 什么是观察者模式?
观察者模式是一种行为设计模式,它允许一个对象(称为主题或被观察者)在状态发生变化时通知所有已注册的观察者对象,而无需知道这些观察者是谁。这种解耦方式使得主题和观察者都可以独立地发展而不影响彼此。

2. 模式的参与者角色介绍

  • Subject (主题):持有状态的对象,负责向观察者发送更新通知。
  • ConcreteSubject (具体主题):实现了Subject接口,维护一个观察者列表,并在状态变化时通知它们。
  • Observer (观察者):定义了一个更新接口,以便主题可以通知观察者。
  • ConcreteObserver (具体观察者):实现Observer接口,根据主题的状态更新自身的行为。

2.2 UML类图和时序图

在这里插入图片描述在这里插入图片描述

2.3 核心原则

1. 抽象耦合
观察者模式的核心在于实现抽象耦合,即主题和观察者之间通过接口进行通信,而不是直接引用具体的实现。这样做的好处是可以增加系统的灵活性和可扩展性。

2. 推送与拉取数据
观察者模式有两种常见的数据传递方式:

  • 推送数据:主题在通知观察者时直接传递更新后的数据。
  • 拉取数据:观察者在接收到更新通知后,主动从主题中获取最新的数据。

2.4 使用场景

1. 动态维护观察关系的例子
例如,在一个新闻系统中,用户可以订阅不同的新闻类别。当新闻类别中有新的新闻时,系统会自动通知所有订阅了该类别的用户。

2. 观察者模式适用的典型场景

  • UI组件:例如按钮点击事件,当按钮被点击时,触发一系列相关的UI更新。
  • 数据绑定:当模型数据发生变化时,视图自动更新。
  • 缓存管理:当缓存中某些数据过期时,自动更新缓存中的数据。

3. 观察者模式与其他模式的关系

3.1 与工厂模式

观察者模式通常不直接与工厂模式相结合,但在某些情况下,可以使用工厂模式来创建观察者对象。例如,可以有一个工厂类用于根据不同的需求创建不同类型的观察者。

3.2 与策略模式

策略模式允许算法的动态替换。观察者模式可以与策略模式结合使用,以提供不同的观察策略。例如,一个主题可以根据不同的情况选择不同的观察者行为。

3.3 与组合模式

组合模式用来构建树形结构以表示“部分-整体”的层次结构。观察者模式可以与组合模式结合使用,以创建复杂的观察者网络。例如,一个主题可以有多个层级的观察者,这些观察者按照一定的层次结构组织起来。


接下来是第四和第五部分的内容草稿:


4. 观察者模式的实现

4.1 Java内置支持

1. java.util.Observable
java.util.Observable 是Java标准库中提供的一个实现观察者模式的类。它实现了观察者模式的主题部分,并提供了添加观察者、移除观察者以及通知观察者的方法。

2. 主要方法

  • addObserver(Observer o): 添加一个观察者。
  • deleteObserver(Observer o): 移除一个观察者。
  • notifyObservers(): 通知所有的观察者。
  • notifyObservers(Object arg): 通知所有的观察者,并传递参数。

3. java.util.Observer 接口
java.util.Observer 是Java标准库中提供的一个接口,它定义了一个方法 update(Observable o, Object arg),该方法用于接收来自主题的通知。

4. 主要方法

  • update(Observable o, Object arg): 当主题发生变化时调用此方法,o 是主题对象,arg 是可选的数据。

4.2 自定义实现

1. 主题接口与具体主题类
在自定义实现中,我们可以定义自己的主题接口和具体主题类。

主题接口

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

具体主题类

public class ConcreteSubject implements Subject {private List<Observer> observers = new ArrayList<>();private int state;@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(this);}}public void setState(int state) {this.state = state;notifyObservers();}public int getState() {return state;}
}

2. 观察者接口与具体观察者类
同样,我们也可以定义自己的观察者接口和具体观察者类。

观察者接口

public interface Observer {void update(Subject subject);
}

具体观察者类

public class ConcreteObserver implements Observer {private String name;private ConcreteSubject subject;public ConcreteObserver(String name, ConcreteSubject subject) {this.name = name;this.subject = subject;}@Overridepublic void update(Subject subject) {System.out.println(name + " received: " + ((ConcreteSubject) subject).getState());}
}

4.3 代码示例

1. 创建主题

ConcreteSubject subject = new ConcreteSubject();

2. 注册观察者

ConcreteObserver observer1 = new ConcreteObserver("Observer 1", subject);
ConcreteObserver observer2 = new ConcreteObserver("Observer 2", subject);subject.registerObserver(observer1);
subject.registerObserver(observer2);

3. 通知观察者

subject.setState(10); // 这将触发通知

4. 示例运行结果分析
setState() 方法被调用时,所有的观察者都将被通知。输出结果类似于:

Observer 1 received: 10
Observer 2 received: 10

5. 高级话题

5.1 异步观察者模式

1. 异步通知机制
异步观察者模式是指在通知观察者时使用非阻塞的方式。这种方式可以避免在通知大量观察者时导致主线程阻塞。

2. 实现细节
为了实现异步通知,可以使用线程池来执行通知操作,或者使用回调机制来异步地更新观察者。

5.2 线程安全问题

1. 并发访问的处理
由于观察者模式涉及到多个线程的并发访问,因此需要确保线程安全。如果不正确地处理并发访问,可能会导致数据不一致或程序崩溃等问题。

2. 线程安全的解决方案

  • 同步块: 使用 synchronized 关键字来同步对共享资源的访问。
  • 使用工具类: 如 ConcurrentHashMapCopyOnWriteArrayList 等线程安全的集合类。
  • 显式锁: 使用 ReentrantLockReadWriteLock 来控制对资源的访问。

5.3 性能考量

1. 性能优化技巧

  • 减少不必要的通知: 只有当状态真正发生变化时才通知观察者。
  • 使用缓存: 对于频繁查询的数据,可以使用缓存来减少计算成本。
  • 限制观察者的数量: 减少过多的观察者,以降低通知开销。

2. 最佳实践

  • 定期清理观察者列表: 避免不再需要的观察者仍然存在于列表中。
  • 合理选择数据传输方式: 根据实际情况选择推送或拉取数据。
  • 考虑使用第三方库: 如使用 Spring Framework 的事件传播机制。

6. 案例研究

6.1 股票报价系统

1. 系统架构概览
股票报价系统是一个典型的使用观察者模式的应用场景。在这个系统中,客户端(观察者)订阅感兴趣的股票信息,服务器端(主题)则负责收集股票价格的实时数据,并在数据发生变化时通知所有订阅的客户端。

系统组件

  • 服务器端: 收集和存储股票价格数据。
  • 客户端: 显示股票价格信息,订阅特定股票的价格更新。

2. 关键代码片段
服务器端(主题)实现

public class StockPriceService implements Subject {private Map<String, Double> stockPrices = new HashMap<>();private List<Observer> observers = new ArrayList<>();@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(this);}}public void setStockPrice(String stockSymbol, double price) {stockPrices.put(stockSymbol, price);notifyObservers();}public Map<String, Double> getStockPrices() {return stockPrices;}
}

客户端(观察者)实现

public class StockPriceDisplay implements Observer {private StockPriceService service;private Map<String, Double> stockPrices;public StockPriceDisplay(StockPriceService service) {this.service = service;this.stockPrices = new HashMap<>();service.registerObserver(this);}@Overridepublic void update(Subject subject) {if (subject instanceof StockPriceService) {StockPriceService stockPriceService = (StockPriceService) subject;stockPrices = stockPriceService.getStockPrices();displayStockPrices();}}private void displayStockPrices() {stockPrices.forEach((symbol, price) -> System.out.println("Stock " + symbol + ": " + price));}
}

3. 测试与调试
为了确保系统正常工作,可以通过单元测试来验证服务器端和客户端的功能是否正确实现。此外,还可以设置断点进行调试,检查数据流是否符合预期。

6.2 天气预报系统

1. 设计思路
天气预报系统是一个实时监测天气变化并通知用户的系统。它包含一个气象站(主题),负责收集天气数据,以及多个客户端(观察者),用于显示天气信息。

2. 代码实现
气象站(主题)实现

public class WeatherStation implements Subject {private double temperature;private List<Observer> observers = new ArrayList<>();@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(this);}}public void setTemperature(double temperature) {this.temperature = temperature;notifyObservers();}public double getTemperature() {return temperature;}
}

客户端(观察者)实现

public class TemperatureDisplay implements Observer {private WeatherStation weatherStation;private double temperature;public TemperatureDisplay(WeatherStation weatherStation) {this.weatherStation = weatherStation;weatherStation.registerObserver(this);}@Overridepublic void update(Subject subject) {if (subject instanceof WeatherStation) {WeatherStation station = (WeatherStation) subject;temperature = station.getTemperature();displayTemperature();}}private void displayTemperature() {System.out.println("Current temperature: " + temperature + "°C");}
}

3. 运行效果展示
当气象站的温度发生变化时,所有注册的客户端将立即更新显示的温度值。


7. 观察者模式的变体

7.1 事件监听器模式

1. 与观察者模式的区别
事件监听器模式与观察者模式相似,但更侧重于事件的处理。在事件监听器模式中,事件源(类似于主题)产生事件,而事件监听器(类似于观察者)响应这些事件。

2. 事件驱动编程
事件驱动编程是一种编程范式,其中程序的执行由外部事件驱动,而不是按预定的顺序执行。这种模式非常适合GUI应用程序和Web应用。

7.2 发布-订阅模式

1. 发布者与订阅者模型
发布-订阅模式是一种消息传递模式,其中发布者发送消息,订阅者接收消息。中间件(通常是消息总线)负责消息的路由。

2. 中间件的角色
中间件作为发布者和订阅者之间的桥梁,它可以是简单的消息队列,也可以是复杂的分布式消息系统。

7.3 其他相关模式

1. 命令模式
命令模式封装了一个请求作为对象,从而使你可用不同的请求对客户端进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

2. 责任链模式
责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。


8. 实战经验分享

8.1 常见错误与陷阱

1. 错误处理

  • 空指针异常: 必须确保在调用观察者的方法之前检查它们是否已经被正确注册。
  • 内存泄漏: 如果观察者没有被正确地从主题中移除,可能会导致内存泄漏。

2. 内存泄漏

  • 确保观察者在不需要时被移除
  • 使用弱引用或软引用来管理观察者列表

8.2 调试技巧

1. 日志记录

  • 使用日志框架,如 Log4j 或 SLF4J,记录关键信息,以便于追踪问题。

2. 单元测试

  • 编写单元测试,确保观察者和主题之间的交互按预期工作。

8.3 重构建议

1. 重构时机

  • 当发现代码冗余或重复时
  • 当发现性能瓶颈时

2. 重构策略

  • 分离关注点,确保主题和观察者职责清晰。
  • 使用设计模式,如装饰者模式,来增强观察者的功能。

9. 总结与展望

1. 总结要点

  • 观察者模式的关键点回顾
    • 观察者模式是一种行为设计模式,用于实现一对多的依赖关系。
    • 主题和观察者通过接口进行通信,实现解耦。
    • 观察者模式可以应用于多种场景,如股票报价系统和天气预报系统。

2. 未来趋势

  • 观察者模式的发展方向
    • 随着异步编程和事件驱动架构的流行,观察者模式的应用将更加广泛。
    • 更高效的并发处理机制将被引入到观察者模式中。

3. 新兴技术的影响

  • 新兴技术,如微服务和容器化,将进一步推动观察者模式在分布式系统中的应用。

本文详细介绍了23种设计模式的基础知识,帮助读者快速掌握设计模式的核心概念,并找到适合实际应用的具体模式:
【设计模式入门】设计模式全解析:23种经典模式介绍与评级指南(设计师必备)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 前端案例:Alloy Team|腾讯全端项目(响应式)
  • 【项目】基于Vue3.2+ElementUI Plus+Vite 通用后台管理系统
  • 安全测试参考标准
  • springboot 整合spring-boot-starter-data-elasticsearch
  • FPGA开发——UART回环实现之接收模块的设计
  • 嵌入式人工智能ESP32(4-PWM呼吸灯)
  • qemu的VNC协议(RFB协议)
  • Django后台数据获取展示
  • Android常用控件Button
  • conda install vs pip install
  • JavaScript基础——函数
  • 使用Python编写AI程序,让机器变得更智能
  • Qt框架学习04——元对象系统
  • Jakarta Servlet 到 SpringMVC
  • 【研发日记】嵌入式处理器技能解锁(四)——TI C2000 DSP的Memory
  • go append函数以及写入
  • HomeBrew常规使用教程
  • js数组之filter
  • Spark VS Hadoop:两大大数据分析系统深度解读
  • 初探 Vue 生命周期和钩子函数
  • 二维平面内的碰撞检测【一】
  • 聊一聊前端的监控
  • 马上搞懂 GeoJSON
  • 巧用 TypeScript (一)
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 正则学习笔记
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • Nginx实现动静分离
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • # .NET Framework中使用命名管道进行进程间通信
  • # windows 运行框输入mrt提示错误:Windows 找不到文件‘mrt‘。请确定文件名是否正确后,再试一次
  • (~_~)
  • (02)Unity使用在线AI大模型(调用Python)
  • (1)(1.9) MSP (version 4.2)
  • (Forward) Music Player: From UI Proposal to Code
  • (java)关于Thread的挂起和恢复
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (转)MVC3 类型“System.Web.Mvc.ModelClientValidationRule”同时存在
  • (转)人的集合论——移山之道
  • (转)原始图像数据和PDF中的图像数据
  • (轉貼)《OOD启思录》:61条面向对象设计的经验原则 (OO)
  • .net 7和core版 SignalR
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .NET框架
  • .NET中两种OCR方式对比
  • .py文件应该怎样打开?
  • @KafkaListener注解详解(一)| 常用参数详解
  • @reference注解_Dubbo配置参考手册之dubbo:reference
  • [2023年]-hadoop面试真题(一)
  • [Android]常见的数据传递方式
  • [AR Foundation] 人脸检测的流程
  • [C\C++]读入优化【技巧】
  • [C++初阶]vector的初步理解
  • [C++打怪升级]--学习总目录