观察者模式(Observer Pattern)详解
文章目录
- 1. 什么是观察者模式?
- 2. 为什么需要观察者模式?
- 3. 观察者模式的核心概念
- 4. 观察者模式的结构
- 5. 观察者模式的基本实现
- 简单的气象站示例
- 6. 观察者模式的进阶实现
- 推模型 vs 拉模型
- 6.1 推模型(Push Model)
- 6.2 拉模型(Pull Model)
- 7. 观察者模式的复杂实现
- 7.1 在线商店库存通知系统
- 7.2 事件监听系统
- 8. 观察者模式在Java中的实际应用
- 8.1 Java原生观察者模式
- 8.2 JavaBeans的PropertyChangeListener
- 8.3 Java Swing事件监听模型
- 9. 观察者模式的优缺点
- 9.1 优点
- 9.2 缺点
- 10. 何时使用观察者模式?
- 11. 观察者模式与其他设计模式的比较
- 11.1 观察者模式 vs 发布-订阅模式
- 11.2 观察者模式 vs 中介者模式
- 11.3 观察者模式 vs 策略模式
- 12. 常见问题与回答
- Q1: 如何避免观察者模式中的循环依赖问题?
- Q2: 如何处理观察者模式中的内存泄漏问题?
- Q3: 观察者模式的通知有哪些不同策略?
- Q4: 观察者模式如何处理线程安全问题?
- 13. 观察者模式的实现变体
- 13.1 异步观察者模式
- 13.2 优先级观察者模式
- 14. 总结
1. 什么是观察者模式?
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,它的所有依赖者(观察者)都会收到通知并自动更新。
观察者模式又被称为发布-订阅(Publish/Subscribe)模式、模型-视图(Model-View)模式或源-监听器(Source-Listener)模式。
2. 为什么需要观察者模式?
在以下情况下,观察者模式特别有用:
- 当一个对象的改变需要同时改变其他对象时:观察者模式可以实现这种一对多的通知机制
- 当一个对象必须通知其他对象,而它又不需要知道这些对象是谁时:主题只需要知道观察者实现了特定接口
- 当你需要维护对象之间的一致性时:不必使各个对象紧密耦合
- 当抽象模型有两个方面,其中一个方面依赖于另一个方面时:将这两者封装在独立的对象中允许您分别使用和修改它们
3. 观察者模式的核心概念
观察者模式主要涉及以下角色:
-
主题(Subject):
- 知道它的观察者,可以有任意多个观察者观察同一个主题
- 提供注册和删除观察者对象的接口
-
具体主题(Concrete Subject):
- 存储观察者感兴趣的状态
- 当状态发生变化时通知观察者
-
观察者(Observer):
- 为那些在主题状态发生改变时需获得通知的对象定义一个更新接口
-
具体观察者(Concrete Observer):
- 实现观察者更新接口以响应主题状态的变化
- 保持与主题的一致性
4. 观察者模式的结构
观察者模式的UML类图如下:
+----------------+ +----------------+
| Subject |<>----->| Observer |
+----------------+ +----------------+
| attach() | | update() |
| detach() | +----------------+
| notify() | ^
+----------------+ |^ || |
+----------------+ +-------------------+
| ConcreteSubject| | ConcreteObserver |
+----------------+ +-------------------+
| getState() | | update() |
| setState() | | observerState |
+----------------+ +-------------------+
5. 观察者模式的基本实现
简单的气象站示例
下面是一个简单的气象站示例,展示了观察者模式的基本实现。
首先,定义观察者接口:
// 观察者接口
public interface Observer {void update(float temperature, float humidity, float pressure);
}
然后,定义主题接口:
// 主题接口
public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();
}
接着,实现具体主题(气象数据):
// 具体主题 - 气象数据
public class WeatherData implements Subject {private List<Observer> observers;private float temperature;private float humidity;private float pressure;public WeatherData() {observers = new ArrayList<>();}@Overridepublic void registerObserver(Observer observer) {if (!observers.contains(observer)) {observers.add(observer);}}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(temperature, humidity, pressure);}}// 当气象测量数据改变时,通知观察者public void measurementsChanged() {notifyObservers();}// 设置气象测量数据public void setMeasurements(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;measurementsChanged();}
}
然后,实现具体观察者(显示器):
// 具体观察者 - 当前状况显示
public class CurrentConditionsDisplay implements Observer {private float temperature;private float humidity;private Subject weatherData;public CurrentConditionsDisplay(Subject weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}@Overridepublic void update(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;display();}public void display() {System.out.println("当前状况: 温度 " + temperature + "°C, 湿度 " + humidity + "%");}
}// 具体观察者 - 统计信息显示
public class StatisticsDisplay implements Observer {private float maxTemp = 0.0f;private float minTemp = 200.0f;private float sumTemp = 0.0f;private int countReadings = 0;private Subject weatherData;public StatisticsDisplay(Subject weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}@Overridepublic void update(float temperature, float humidity, float pressure) {sumTemp += temperature;countReadings++;if (temperature > maxTemp) {maxTemp = temperature;}if (temperature < minTemp) {minTemp = temperature;}display();}public void display() {System.out.println("统计信息: 平均温度 " + (sumTemp / countReadings) + "°C, 最高温度 " + maxTemp + "°C, 最低温度 " + minTemp + "°C");}
}// 具体观察者 - 天气预报显示
public class ForecastDisplay implements Observer {private float currentPressure = 29.92f;private float lastPressure;private Subject weatherData;public ForecastDisplay(Subject weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}@Overridepublic 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("天气可能转凉,注意保暖");}}
}
最后,创建一个测试类来演示观察者模式:
public class WeatherStation {public static void main(String[] args) {// 创建主题WeatherData weatherData = new WeatherData();// 创建观察者CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);// 模拟气象数据变化System.out.println("第一次气象数据更新:");weatherData.setMeasurements(25.0f, 65.0f, 30.4f);System.out.println("\n第二次气象数据更新:");weatherData.setMeasurements(27.0f, 70.0f, 29.2f);System.out.println("\n第三次气象数据更新:");weatherData.setMeasurements(20.0f, 90.0f, 29.2f);// 移除一个观察者System.out.println("\n移除统计信息显示后:");weatherData.removeObserver(statisticsDisplay);weatherData.setMeasurements(22.0f, 80.0f, 31.0f);}
}
输出结果:
第一次气象数据更新:
当前状况: 温度 25.0°C, 湿度 65.0%
统计信息: 平均温度 25.0°C, 最高温度 25.0°C, 最低温度 25.0°C
天气预报: 天气状况保持不变第二次气象数据更新:
当前状况: 温度 27.0°C, 湿度 70.0%
统计信息: 平均温度 26.0°C, 最高温度 27.0°C, 最低温度 25.0°C
天气预报: 天气可能转凉,注意保暖第三次气象数据更新:
当前状况: 温度 20.0°C, 湿度 90.0%
统计信息: 平均温度 24.0°C, 最高温度 27.0°C, 最低温度 20.0°C
天气预报: 天气状况保持不变移除统计信息显示后:
当前状况: 温度 22.0°C, 湿度 80.0%
天气预报: 天气正在好转!
6. 观察者模式的进阶实现
推模型 vs 拉模型
观察者模式有两种常见的实现方式:推模型和拉模型。
6.1 推模型(Push Model)
在推模型中,主题对象向观察者推送所有数据,不管观察者是否需要。上面的例子使用的就是推模型。
6.2 拉模型(Pull Model)
在拉模型中,主题仅通知观察者有数据更新,由观察者自己决定获取哪些数据。下面我们将上面的例子修改为拉模型:
首先,修改观察者接口,update方法不再传递数据:
// 观察者接口 - 拉模型
public interface Observer {void update();
}
修改主题接口,添加获取数据的方法:
// 主题接口 - 拉模型
public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();// 获取数据的方法float getTemperature();float getHumidity(<