目录
一、引言:为什么需要观察者模式?
在软件开发中,对象之间的耦合度越高,代码的维护性和扩展性越差。例如:
- 事件处理:当一个对象的状态变化需要通知多个其他对象时(如GUI按钮点击触发多个事件)。
- 数据同步:当数据源更新时,多个依赖该数据的对象需要自动刷新(如股票价格变动通知投资者)。
传统方案中,对象间直接调用会导致强耦合,而观察者模式通过解耦解决了这一问题。
二、观察者模式的核心原理
1. 角色划分
- Subject(主题):维护观察者列表,提供注册、移除和通知观察者的接口。
- Observer(观察者):定义更新接口,接收主题的通知并自动更新。
- ConcreteSubject(具体主题):实现主题接口,管理观察者列表,状态变化时通知观察者。
- ConcreteObserver(具体观察者):实现观察者接口,定义收到通知后的具体行为。
2. 类图关系
[Subject] <-- [ConcreteSubject]
▼
List<Observer>
▲
[Observer] --> [ConcreteObserver]
三、经典案例解析
案例1:天气监测系统
背景:气象站实时采集温度、湿度数据,多个显示设备(如当前状况、统计图表)需同步更新。
实现步骤:
- 定义主题接口:
interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); }
- 实现具体主题:
class WeatherData implements Subject { private List<Observer> observers; private float temperature; public void setMeasurements(float temp, float hum) { this.temperature = temp; notifyObservers(); } public void notifyObservers() { for (Observer observer : observers) { observer.update(temperature); } } // 注册、移除观察者方法省略 }
- 定义观察者接口:
interface Observer { void update(float temperature); }
- 实现具体观察者:
class CurrentConditionsDisplay implements Observer { public void update(float temperature) { System.out.println("当前温度:" + temperature + "°C"); } }
效果:
- 新增显示设备(如统计图表)只需实现
Observer
接口,无需修改WeatherData
。 - 主题与观察者解耦,支持动态扩展。
案例2:股票价格监控系统
背景:投资者订阅股票信息,当价格变动时需实时通知所有订阅者。
实现亮点:
- 主题:
Stock
类维护价格和观察者列表。 - 观察者:
Investor
类实现更新逻辑,如触发买入/卖出操作。 - 动态订阅:投资者可随时添加或取消订阅。
代码片段:
class Stock implements Subject {
private List<Observer> investors = new ArrayList<>();
private double price;
public void setPrice(double newPrice) {
this.price = newPrice;
notifyObservers();
}
public void notifyObservers() {
for (Observer investor : investors) {
investor.update(price);
}
}
}
案例3:MVC架构中的模型-视图分离
背景:模型层(Model)数据变化时,多个视图层(如柱状图、饼图)需自动更新。
实现逻辑:
- 模型:作为主题,存储数据并通知视图。
- 视图:作为观察者,订阅模型更新。
- 优势:视图与模型解耦,支持任意数量的视图扩展。
示例:
class Model implements Subject {
private int data;
public void setData(int newData) {
this.data = newData;
notifyObservers();
}
}
class BarChart implements Observer {
public void update(int data) {
System.out.println("柱状图更新:" + data);
}
}
案例4:Java内置事件监听机制
背景:GUI按钮点击事件需触发多个操作(如弹窗、日志记录)。
实现原理:
- 事件源:按钮作为主题,触发事件。
- 事件监听器:观察者实现
ActionListener
接口,处理事件。
代码示例:
JButton button = new JButton("Click");
button.addActionListener(e -> System.out.println("按钮被点击!"));
四、进阶技巧与注意事项
1. 避免循环依赖
若观察者在update()
中修改主题状态,可能导致无限递归。解决方案:
- 限制通知层级(如仅通知一次)。
- 使用标志位判断是否正在通知。
2. 异步通知
在复杂系统中,同步通知可能阻塞主线程。可通过异步回调或消息队列优化:
new Thread(() -> notifyObservers()).start();
3. Java内置支持
JDK提供java.util.Observable
和java.util.Observer
,但存在以下问题:
- 观察者与主题强绑定,难以扩展。
- 建议自定义接口,提升灵活性。
五、对比其他设计模式
模式 | 核心目标 | 观察者模式适用场景 |
---|---|---|
策略模式 | 算法替换 | 需动态切换行为(如排序算法) |
中介者模式 | 多对象通信协调 | 复杂对象网状关系(如聊天室) |
发布-订阅 | 事件分发(如消息队列) | 高并发、异步事件处理 |
六、总结与建议
观察者模式通过解耦和广播机制,完美解决对象间一对多依赖问题。在实际开发中:
- 优先解耦:将主题与观察者职责分离,避免直接调用。
- 灵活扩展:通过接口定义,支持动态添加新观察者。
- 谨慎处理循环依赖:设计时需考虑通知顺序和条件。
实践建议:
- 初学者从简单案例(如天气系统)入手,理解核心逻辑。
- 在职开发者可研究开源框架(如RxJava、Spring事件机制)中的实现。