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

深入理解 Java 观察者模式:原理、实现与应用

       在软件开发领域,设计模式堪称开发者智慧的凝练结晶,它们为解决各类常见编程难题提供了行之有效的方案。观察者模式(Observer Pattern)作为行为型设计模式的重要一员,在处理对象间依赖关系与事件通知方面表现卓越。本文将深入剖析 Java 中的观察者模式,涵盖其核心概念、实现途径、优劣之处及适用场景,助力读者全方位掌握并灵活运用这一关键设计模式。​

一、观察者模式核心概念​

(一)定义与核心思想​

观察者模式定义了对象间的一对多依赖关系,当某一对象(被观察者,Subject)的状态发生变动时,所有依赖于它的对象(观察者,Observer)都会收到通知并自动更新。其核心在于解耦观察者与被观察者,让二者借助抽象接口进行交互,而非直接耦合,以此提升系统的灵活性与可扩展性。​

(二)角色划分​

观察者模式主要涉及四个关键角色:​

  1. 抽象被观察者(Subject):定义了添加、移除观察者以及通知观察者的接口或抽象类,维护着一个观察者列表,当自身状态变化时,负责通知列表中的所有观察者。​
  2. 具体被观察者(Concrete Subject):实现抽象被观察者的接口,具体负责管理自身状态,状态变化时,调用通知方法告知观察者。​
  3. 抽象观察者(Observer):定义了用于接收被观察者通知的更新接口,收到通知后执行相应操作。​
  4. 具体观察者(Concrete Observer):实现抽象观察者的更新接口,通常持有对具体被观察者的引用,以便在收到通知时获取被观察者状态并处理。​

二、Java 中观察者模式的实现方式​

在 Java 里,实现观察者模式主要有两种常见途径:一种是借助 JDK 内置的观察者相关类与接口,另一种则是自定义实现。接下来将分别详细阐述。​

(一)JDK 内置的观察者模式实现​

Java 的 java.util 包提供了 Observable 类和 Observer 接口,方便快速实现观察者模式。​

1. Observable 类​

Observable 类是抽象被观察者的具体实现,具备以下主要方法:​

  • addObserver(Observer o):向观察者列表中添加一个观察者。​
  • deleteObserver(Observer o):从观察者列表中移除一个观察者。​
  • notifyObservers():通知所有已注册的观察者,但调用此方法前需先调用setChanged()方法,用以标记被观察者状态已改变。​
  • notifyObservers(Object arg):带参数的通知方法,将参数传递给观察者。​
  • setChanged():设置内部标志,表明被观察者状态已改变,只有调用此方法后再调用notifyObservers(),观察者才会收到通知。​
  • clearChanged():清除状态改变标志。​

2. Observer 接口​

Observer 接口定义了观察者的更新方法,仅有一个方法:​

  • update(Observable o, Object arg):当被观察者状态变化并通知观察者时,该方法被调用。其中,o是发出通知的被观察者对象,arg是传递给观察者的参数(若有)。​

3. 使用步骤示例​

以简单的天气数据监测系统为例,展示如何运用 JDK 内置的观察者模式。​

步骤 1:创建具体被观察者(WeatherData)​

import java.util.Observable;​

public class WeatherData extends Observable {​

private float temperature;​

private float humidity;​

private float pressure;​

public void setWeatherData(float temperature, float humidity, float pressure) {​

this.temperature = temperature;​

this.humidity = humidity;​

this.pressure = pressure;​

// 设置状态改变标志​

setChanged();​

// 通知所有观察者​

notifyObservers();​

// 或者传递具体参数​

// notifyObservers(new WeatherInfo(temperature, humidity, pressure));​

}​

// 获取温度、湿度、气压的方法...​

}​

步骤 2:创建具体观察者(CurrentConditionsDisplay)​

import java.util.Observable;​

import java.util.Observer;​

public class CurrentConditionsDisplay implements Observer {​

private float temperature;​

private float humidity;​

private Observable observable;​

public CurrentConditionsDisplay(Observable observable) {​

this.observable = observable;​

// 将当前观察者添加到被观察者的列表中​

observable.addObserver(this);​

}​

@Override​

public void update(Observable o, Object arg) {​

if (o instanceof WeatherData) {​

WeatherData weatherData = (WeatherData) o;​

this.temperature = weatherData.getTemperature();​

this.humidity = weatherData.getHumidity();​

display();​

}​

}​

private void display() {​

System.out.println("当前天气状况:温度 " + temperature + "℃,湿度 " + humidity + "%");​

}​

}​

步骤 3:客户端使用​

public class Client {​

public static void main(String[] args) {​

WeatherData weatherData = new WeatherData();​

CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);​

// 模拟天气数据变化​

weatherData.setWeatherData(25.0f, 60.0f, 1013.0f);​

weatherData.setWeatherData(28.0f, 55.0f, 1010.0f);​

}​

}​

4. 注意事项​

  • Observable 类是具体类而非接口,这在一定程度上限制了其扩展性,因为 Java 不支持多继承,若一个类已继承其他类,便无法再继承 Observable 类。​
  • 调用 notifyObservers () 方法前,务必先调用 setChanged () 方法,否则观察者不会收到通知。这是由于 Observable 类内部通过布尔变量 changed 判断是否通知观察者,setChanged () 方法将其设为 true,notifyObservers () 方法调用后会将其设为 false(除非使用带参数版本且参数为 null)。​

(二)自定义实现观察者模式​

尽管 JDK 提供了内置的观察者实现,但在某些场景下,我们可能期望更灵活地定义抽象被观察者和抽象观察者接口,或者避免依赖 JDK 中的 Observable 类(比如被观察者需继承其他类时)。此时,可自定义实现观察者模式。​

1. 定义抽象观察者接口(Observer)​

public interface Observer {​

void update(Object data);​

}​

2. 定义抽象被观察者接口(Subject)​

public interface Subject {​

void registerObserver(Observer observer);​

void removeObserver(Observer observer);​

void notifyObservers(Object data);​

}​

3. 实现具体被观察者(ConcreteSubject)​

import java.util.ArrayList;​

import java.util.List;​

public class ConcreteSubject implements Subject {​

private List<Observer> observers = new ArrayList<>();​

private Object data;​

@Override​

public void registerObserver(Observer observer) {​

observers.add(observer);​

}​

@Override​

public void removeObserver(Observer observer) {​

observers.remove(observer);​

}​

@Override​

public void notifyObservers(Object data) {​

this.data = data;​

for (Observer observer : observers) {​

observer.update(data);​

}​

}​

public void setData(Object data) {​

notifyObservers(data);​

}​

}​

4. 实现具体观察者(ConcreteObserver)​

public class ConcreteObserver implements Observer {​

private String name;​

public ConcreteObserver(String name) {​

this.name = name;​

}​

@Override​

public void update(Object data) {​

System.out.println("观察者" + name + "接收到数据:" + data);​

}​

}​

5. 客户端使用示例​

public class CustomObserverClient {​

public static void main(String[] args) {​

ConcreteSubject subject = new ConcreteSubject();​

ConcreteObserver observer1 = new ConcreteObserver("A");​

ConcreteObserver observer2 = new ConcreteObserver("B");​

subject.registerObserver(observer1);​

subject.registerObserver(observer2);​

subject.setData("新数据来了!");​

subject.removeObserver(observer2);​

subject.setData("再次更新数据!");​

}​

}​

(三)两种实现方式的对比​

特性​

JDK 内置实现​

自定义实现​

抽象程度​

使用具体类 Observable 和接口 Observer,Observable 设计存在局限性(如非接口)​

完全自定义抽象接口,可依需求灵活设计接口方法​

扩展性​

因 Observable 是具体类,若被观察者需继承其他类则无法使用,扩展性受限​

被观察者实现自定义 Subject 接口,可自由继承其他类,扩展性更佳​

依赖关系​

依赖 java.util 包中的类和接口​

不依赖 JDK 特定类,可在更广泛环境中使用​

学习成本​

简单易用,适用于快速实现简单观察者模式场景​

需手动定义接口和实现,适用于复杂场景或高度定制需求​

三、观察者模式的优缺点​

(一)优点​

  1. 解耦观察者和被观察者:观察者与被观察者通过抽象接口交互,彼此无需了解对方具体实现,降低代码耦合度。被观察者状态变化时,观察者自动接收通知,无需主动查询,提升系统灵活性。​
  2. 支持广播通信:被观察者能同时通知多个观察者,实现一对多通信模式,契合消息推送、状态变更通知等事件广播场景。​
  3. 提高可维护性和扩展性:观察者与被观察者分离,新增或修改观察者行为不影响被观察者代码,反之亦然。系统更易维护和扩展,符合开闭原则(对扩展开放,对修改关闭)。​

(二)缺点​

  1. 通知顺序问题:被观察者通知观察者时,通常按注册顺序依次调用更新方法。若观察者间存在依赖关系或对通知顺序有特定要求,可能需额外处理,否则易导致意外结果。​
  2. 潜在的性能问题:若观察者数量众多,或每个观察者的更新方法执行耗时较长,被观察者通知所有观察者时可能耗时过多,引发性能下降,极端情况下甚至导致线程阻塞。​
  3. 过度使用可能导致复杂度增加:观察者模式虽能解耦对象,但系统中过度使用会使对象间依赖关系复杂,难以追踪和维护。尤其当多个被观察者和观察者存在复杂交互时,可能形成难以管理的网状结构。​

四、观察者模式的适用场景​

(一)当一个对象的状态变化需要通知多个对象时​

这是观察者模式最典型的应用场景。比如在股票交易系统中,某只股票价格变动时,需通知所有关注该股票的用户;新闻订阅系统里,有新新闻发布时,要通知所有订阅该新闻频道的用户。​

(二)当系统需要实现事件驱动机制时​

观察者模式可用于实现事件监听与处理机制。以 GUI 编程为例,按钮点击事件、窗口关闭事件等,都可通过观察者模式实现。用户触发事件(相当于被观察者状态变化)时,注册的事件监听器(相当于观察者)接收通知并执行相应处理逻辑。​

(三)当需要解耦具有依赖关系的对象时​

若两个或多个对象存在一对多的依赖关系,即一个对象变化影响多个对象,使用观察者模式可将它们解耦,使其通过抽象接口交互,而非直接依赖具体实现,提升系统灵活性与可维护性。​

(四)当需要实现数据的实时更新和同步时​

在分布式系统或实时数据处理系统中,常需将数据变化实时同步到多个客户端或模块。观察者模式能便捷实现数据实时更新,数据源(被观察者)数据变化时,所有订阅该数据源的客户端(观察者)自动接收通知并更新自身数据。​

五、实际应用案例分析​

(一)Swing 中的事件处理​

在 Java Swing GUI 开发中,观察者模式广泛应用于事件处理。例如,用户点击按钮时,按钮对象(被观察者)通知所有注册的动作监听器(观察者)。动作监听器实现 ActionListener 接口(相当于抽象观察者接口),其中的 actionPerformed 方法(相当于 update 方法)在按钮点击时被调用,执行相应事件处理逻辑。​

(二)Spring Framework 中的事件机制​

Spring 框架提供基于观察者模式的事件发布 - 订阅机制。通过 ApplicationEvent 类(抽象事件,相当于被观察者状态变化通知)和 ApplicationListener 接口(观察者接口),开发者可自定义事件和事件监听器。事件发生时,Spring 容器将事件发布给所有注册监听器,监听器执行相应处理逻辑。该机制在 Spring Boot 的自动配置、事务管理等模块均有应用。​

(三)消息中间件中的订阅 - 发布模式​

消息中间件(如 Kafka、RabbitMQ 等)的订阅 - 发布模式(Publish/Subscribe Pattern)与观察者模式极为相似。生产者(相当于被观察者)发布消息到主题(Topic),消费者(相当于观察者)订阅主题并接收消息。尽管订阅 - 发布模式通常借助消息代理(Broker)作为中介,而观察者模式中被观察者和观察者可直接交互,但二者核心思想均为一对多依赖关系和事件通知。​

六、总结与最佳实践​

观察者模式是极为实用的设计模式,通过解耦观察者和被观察者,实现灵活的事件通知机制,适用于多种场景。在 Java 中,既可用 JDK 内置的 Observable 和 Observer 快速实现简单观察者模式,也可通过自定义接口实现更灵活、贴合需求的模式。​

运用观察者模式时,需留意以下最佳实践:​

  1. 合理设计抽象接口:抽象被观察者和抽象观察者的接口应尽量通用,涵盖所有可能操作,避免频繁修改接口。​
  2. 控制观察者数量和更新逻辑:避免注册过多观察者,或在观察者更新方法中执行耗时操作,防止性能问题。​
  3. 处理循环依赖问题:若观察者和被观察者存在循环依赖(如观察者更新时修改被观察者状态,导致再次通知观察者),需谨慎处理,避免陷入无限循环。​
  4. 结合具体场景选择实现方式:依据项目具体需求,选择 JDK 内置实现或自定义实现。若需高度灵活性和扩展性,自定义实现更佳;若场景简单,追求快速实现,JDK 内置实现更便捷。​

深入理解观察者模式的原理、实现方式和适用场景,并遵循最佳实践,开发者便能在软件开发中灵活运用该模式,提升代码质量与系统可维护性。随着技术持续发展,观察者模式也在不断演进,与责任链模式、策略模式等结合,用以解决更复杂的问题。因此,持续学习和实践设计模式,对提升软件开发能力意义重大。

相关文章:

  • 【开发工具】Window安装WSL及配置Vscode获得Linux开发环境
  • npm install下载插件无法更新package.json和package-lock.json文件的解决办法
  • Android组件化 -> Debug模式下,本地构建module模块的AAR和APK
  • 三极管偏置电路分析
  • 51单片机入门教程——AT24C02(I2C 总线)
  • 在PBiCGStab(Preconditioned Bi-Conjugate Gradient Stabilized)算法中处理多个右端项的block版本
  • Github Action部署node项目
  • 论文阅读笔记——ROBOGROUND: Robotic Manipulation with Grounded Vision-Language Priors
  • 一个基于Asp.Net Core + Angular + Bootstrap开源CMS系统
  • 【离线安装python包的方法】
  • Nginx 安全防护与 HTTPS 部署
  • 【基础】Python包管理工具uv使用教程
  • Linux远程管理
  • HHsuite3 的 HHblits 和 HHsearch比较
  • 【上位机——MFC】单文档和多文档视图架构
  • TestStand API 简介
  • 猿人学web端爬虫攻防大赛赛题第7题——动态字体,随风漂移
  • 本地文件批量切片处理与大模型精准交互系统开发指南
  • C# 使用SunnyUI控件 (VS 2019)
  • UE5 渲染思路笔记(角色)
  • 央行宣布优化两项支持资本市场的货币政策工具
  • 牛市早报|金融政策支持稳市场稳预期发布会将举行,商务部:中方决定同意与美方进行接触
  • 我国科研团队发布第四代量子计算测控系统
  • 马斯克的胜利?OpenAI迫于压力放弃营利性转型计划
  • 长沙天心阁举办古琴音乐会:文旅向深,让游客听见城市的底蕴
  • 韩国总统选举民调:共同民主党前党首李在明支持率超46%