Java 设计模式之观察者模式:构建松耦合的事件响应系统
在软件开发中,我们经常需要处理对象之间的依赖关系 —— 当一个对象的状态发生变化时,其他依赖它的对象需要得到通知并做出相应处理。比如微信公众号的推送机制、股票价格变动时的行情更新、UI 组件状态变化时的界面刷新等。观察者模式(Observer Pattern)正是解决这类问题的经典设计模式。
什么是观察者模式?
观察者模式是一种行为型设计模式,它定义了对象之间的一对多依赖关系,使得当一个对象(被观察者)的状态发生改变时,所有依赖它的对象(观察者)都会自动收到通知并更新。
这种模式的核心是松耦合—— 被观察者不需要知道具体的观察者是谁,只需要知道观察者实现了统一的接口。这使得两者可以独立变化,互不影响。
观察者模式的核心角色
观察者模式通常包含以下四个核心角色:
- 被观察者(Subject):也称为主题,维护一个观察者列表,提供添加、删除观察者的方法,以及通知所有观察者的方法
- 观察者(Observer):定义一个更新接口,当被观察者状态变化时,被调用以进行更新
- 具体被观察者(Concrete Subject):实现被观察者接口,当状态发生变化时,通知所有注册的观察者
- 具体观察者(Concrete Observer):实现观察者接口,当收到通知时执行具体的更新操作
观察者模式的工作原理
- 观察者向被观察者注册自己,表明希望接收通知
- 被观察者维护一个观察者列表
- 当被观察者的状态发生变化时,遍历观察者列表,调用每个观察者的更新方法
- 观察者收到通知后,根据被观察者的新状态执行相应操作
- 观察者可以随时取消注册,不再接收通知
观察者模式的代码实现
我们以 "气象站数据发布 - 显示" 系统为例来实现观察者模式。气象站(被观察者)会定期测量温度、湿度和气压,多个显示面板(观察者)需要实时显示这些数据。
1. 观察者接口
首先定义观察者接口,所有观察者都需要实现这个接口:
// 观察者接口
public interface Observer {
// 当被观察者状态变化时,调用此方法更新
void update(float temperature, float humidity, float pressure);
}
2. 被观察者接口
定义被观察者接口,包含管理观察者和通知观察者的方法:
// 被观察者接口
public interface Subject {
// 注册观察者
void registerObserver(Observer observer);
// 移除观察者
void removeObserver(Observer observer);
// 通知所有观察者
void notifyObservers();
}
3. 具体被观察者
实现气象站作为具体被观察者:
import java.util.ArrayList;
import java.util.List;
// 气象站(具体被观察者)
public class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature; // 温度
private float humidity; // 湿度
private float pressure; // 气压
public WeatherStation() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
// 当气象数据更新时调用此方法
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged(); // 数据变化,通知观察者
}
// 数据变化的处理方法
private void measurementsChanged() {
notifyObservers();
}
}
4. 具体观察者
实现不同的显示面板作为具体观察者:
// CurrentConditionsDisplay.java
// 当前天气状况面板
public class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
// 可以保存被观察者的引用,以便后续取消注册
private Subject weatherStation;
public CurrentConditionsDisplay(Subject weatherStation) {
this.weatherStation = weatherStation;
weatherStation.registerObserver(this); // 注册为观察者
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("当前天气状况:温度 " + temperature + "℃,湿度 " + humidity + "%");
}
}
// StatisticsDisplay.java
// 天气统计面板
public class StatisticsDisplay implements Observer {
private float maxTemp = 0.0f;
private float minTemp = Float.MAX_VALUE;
private float tempSum = 0.0f;
private int numReadings;
public StatisticsDisplay(Subject weatherStation) {
weatherStation.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
tempSum += temperature;
numReadings++;
if (temperature > maxTemp) {
maxTemp = temperature;
}
if (temperature < minTemp) {
minTemp = temperature;
}
display();
}
public void display() {
System.out.println("温度统计:平均 " + (tempSum / numReadings) + "℃,最高 " + maxTemp + "℃,最低 " + minTemp + "℃");
}
}
// ForecastDisplay.java
// 天气预报面板
public class ForecastDisplay implements Observer {
private float currentPressure = 1013.25f;
private float lastPressure;
public ForecastDisplay(Subject weatherStation) {
weatherStation.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
public void display() {
System.out.print("天气预报:");
if (currentPressure > lastPressure) {
System.out.println("天气将转好!");
} else if (currentPressure == lastPressure) {
System.out.println("天气将保持不变");
} else {
System.out.println("可能会有降雨");
}
}
}
5. 客户端测试
public class WeatherStationDemo {
public static void main(String[] args) {
// 创建被观察者:气象站
WeatherStation weatherStation = new WeatherStation();
// 创建观察者并注册
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherStation);
StatisticsDisplay statsDisplay = new StatisticsDisplay(weatherStation);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherStation);
// 模拟气象数据更新
System.out.println("\n=== 第一次测量数据 ===");
weatherStation.setMeasurements(25.6f, 65.0f, 1012.5f);
System.out.println("\n=== 第二次测量数据 ===");
weatherStation.setMeasurements(26.2f, 62.0f, 1013.0f);
System.out.println("\n=== 第三次测量数据 ===");
weatherStation.setMeasurements(24.8f, 70.0f, 1011.0f);
// 移除一个观察者
weatherStation.removeObserver(forecastDisplay);
System.out.println("\n=== 第四次测量数据(已移除天气预报面板) ===");
weatherStation.setMeasurements(25.0f, 68.0f, 1012.0f);
}
}
运行结果:
=== 第一次测量数据 ===
当前天气状况:温度 25.6℃,湿度 65.0%
温度统计:平均 25.6℃,最高 25.6℃,最低 25.6℃
天气预报:可能会有降雨
=== 第二次测量数据 ===
当前天气状况:温度 26.2℃,湿度 62.0%
温度统计:平均 25.9℃,最高 26.2℃,最低 25.6℃
天气预报:天气将转好!
=== 第三次测量数据 ===
当前天气状况:温度 24.8℃,湿度 70.0%
温度统计:平均 25.533333℃,最高 26.2℃,最低 24.8℃
天气预报:可能会有降雨
=== 第四次测量数据(已移除天气预报面板) ===
当前天气状况:温度 25.0℃,湿度 68.0%
温度统计:平均 25.4℃,最高 26.2℃,最低 24.8℃
Java 内置的观察者模式
Java 在java.util包中提供了内置的观察者模式实现:Observable类(被观察者)和Observer接口(观察者)。不过需要注意的是,Java 9 之后这两个类已被标记为过时(@Deprecated),推荐使用更灵活的java.beans包下的相关类或自己实现。
使用内置 API 的简单示例:
import java.util.Observable;
import java.util.Observer;
// 具体被观察者
class MyObservable extends Observable {
private int state;
public void setState(int state) {
this.state = state;
setChanged(); // 标记状态已改变
notifyObservers(state); // 通知观察者,可传递数据
}
}
// 具体观察者
class MyObserver implements Observer {
private String name;
public MyObserver(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(name + " 收到更新:" + arg);
}
}
// 测试
public class BuiltInObserverDemo {
public static void main(String[] args) {
MyObservable observable = new MyObservable();
observable.addObserver(new MyObserver("观察者1"));
observable.addObserver(new MyObserver("观察者2"));
observable.setState(100);
observable.setState(200);
}
}
观察者模式的优缺点
优点
- 松耦合:被观察者和观察者之间是抽象耦合,两者可以独立变化
- 可扩展性:新增观察者无需修改被观察者代码,符合开闭原则
- 广播通信:被观察者可以一次性通知所有观察者,高效便捷
- 符合单一职责原则:被观察者专注于状态管理,观察者专注于状态响应
缺点
- 意外更新:观察者可能会收到不需要的更新,需要在设计时处理
- 通知顺序问题:观察者的通知顺序不固定,可能会影响结果
- 内存泄漏风险:如果观察者没有正确移除,可能导致内存泄漏
观察者模式的适用场景
- 当一个对象的改变需要同时改变其他对象,且不知道具体有多少对象需要改变时
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时
- 事件处理系统:如 GUI 中的按钮点击、键盘输入等事件响应
- 消息通知系统:如订阅 - 发布模式、消息队列等
- 状态监控系统:如股票行情、传感器数据监控等
总结
观察者模式通过建立对象间的一对多依赖关系,实现了状态变化的自动通知机制,有效降低了对象间的耦合度。它在现代软件开发中应用广泛,尤其是在事件驱动、分布式系统和响应式编程中。
实现观察者模式时,需要注意合理设计被观察者和观察者的接口,确保系统的灵活性和可扩展性。同时要注意及时移除不再需要的观察者,避免内存泄漏和不必要的更新操作。
掌握观察者模式,能帮助我们设计出更灵活、更易维护的系统,特别是在处理对象间复杂依赖关系时,能显著提升代码质量。