设计模式(二十)行为型:观察者模式详解
设计模式(二十)行为型:观察者模式详解
观察者模式(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 类图:
图解说明:
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图(简化版)
运行说明:
WeatherData
是被观察者,维护状态和观察者列表。- 三个具体观察者(
CurrentConditionsDisplay
,StatisticsDisplay
,ForecastDisplay
)在构造时注册到WeatherData
。 - 当
setMeasurements
被调用时,WeatherData
调用notifyObservers
,遍历所有观察者并调用其update
方法。 - 每个观察者在
update
中获取最新数据并更新自身显示。 - 可动态注销观察者(如
statisticsDisplay.remove()
),之后不再接收通知。
四、总结
特性 | 说明 |
---|---|
核心目的 | 实现对象间一对多的依赖,自动通知状态变化 |
实现机制 | 被观察者维护观察者列表,状态变时广播通知 |
优点 | 解耦、支持广播、动态注册、符合开闭原则 |
缺点 | 可能导致意外更新(级联通知)、观察者过多影响性能、内存泄漏(未注销) |
适用场景 | GUI 事件处理、MVC 架构、消息系统、实时通知、数据绑定 |
不适用场景 | 依赖关系简单、观察者数量极少、性能极度敏感 |
观察者模式使用建议:
- 确保观察者能及时注销,避免内存泄漏。
- 考虑通知顺序和性能,大量观察者时可异步通知。
- Java 内置
java.util.Observable
和java.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);在边缘计算中,设备状态变化触发本地观察者;在量子网络中,量子态的纠缠可视为一种超距观察者机制;在元宇宙中,用户动作作为事件被多个虚拟对象观察。
掌握观察者模式,是设计松耦合、高响应性、可扩展系统的必经之路。作为架构师,应在设计任何需要“状态变化通知”或“组件联动”的场景时,优先考虑观察者模式。它不仅是模式,更是系统生命力的体现——它让系统从“被动调用”走向“主动响应”,从“静态结构”走向“动态演化”,构建出能够感知变化、自动适应、持续进化的智能软件生态。