Appearance
观察者模式(Observer Pattern)笔记
定义
观察者模式是一种行为设计模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
英文原文定义:
The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
核心概念
观察者模式建立了主题(Subject)与观察者(Observer)之间的发布-订阅关系,实现了松耦合的状态同步机制。当主题状态变化时,无需知道具体观察者是谁,只需通知已注册的观察者。
主要角色
- Subject(主题):维护观察者列表,提供注册/注销接口,状态变化时通知观察者
- Observer(观察者):定义更新接口,接收主题通知
- ConcreteSubject(具体主题):存储具体状态,状态改变时通知观察者
- ConcreteObserver(具体观察者):实现更新接口,保持与主题状态一致
优缺点
优点
- 松耦合:主题和观察者之间抽象耦合,彼此不知道对方细节
- 动态关系:可以在运行时动态建立和解除观察关系
- 广播通信:支持一对多通信,一个主题变化可通知多个观察者
- 开闭原则:新增观察者无需修改主题代码
缺点
- 通知顺序不可控:观察者被通知的顺序不确定
- 性能开销:大量观察者或频繁更新可能导致性能问题
- 循环依赖:不当使用可能导致循环调用(观察者更新又触发通知)
- 更新细节不明确:简单的通知机制无法传达状态变化的细节
解决的问题
观察者模式主要解决以下经典问题:
- 状态同步:当对象状态变化需要通知其他对象,且不知道具体有多少对象需要通知时
- 事件处理:需要实现类似事件处理的机制时
- 解耦需求:当需要减少对象间的紧密耦合时
- 实时更新:当一个对象的改变需要同时改变其他对象,且不知道具体有多少对象有待改变时
实现注意事项
推模型 vs 拉模型:
- 推模型:主题将详细数据通过update方法参数发送给观察者
- 拉模型:观察者收到通知后主动从主题拉取所需数据
线程安全:
- 在多线程环境中,观察者的注册/注销和通知过程需要同步
- 考虑使用线程安全的集合类存储观察者列表
防止内存泄漏:
- 观察者长期不注销可能导致内存泄漏(特别是观察者生命周期短于主题时)
- 弱引用(WeakReference)可作为解决方案之一
通知频率控制:
- 实现节流机制避免频繁通知
- 考虑使用"脏标志"只在必要时通知
避免观察者方法异常:
- 某个观察者处理异常不应影响其他观察者
- 考虑异常处理机制
实现变体
- 经典实现:严格遵循GoF定义的接口分离设计
- 事件总线/消息系统:扩展为更通用的发布-订阅系统
- 反应式流:支持背压(back-pressure)的现代变体(如RxJava)
- 语言特性集成:某些语言内置观察者模式支持(如C#事件委托)
与其他模式的关系
相似模式区分
发布-订阅模式:
- 观察者模式:主题直接维护观察者列表,直接通知
- 发布-订阅:通过消息代理解耦,发布者和订阅者互不知晓
中介者模式:
- 观察者模式:通过主题协调观察者
- 中介者模式:通过中介对象封装对象间的交互
责任链模式:
- 观察者模式:所有观察者都会收到通知
- 责任链模式:请求沿链传递直到被处理
常见搭配组合
- 观察者 + 中介者:用中介者管理复杂的观察关系
- 观察者 + 备忘录:观察者记录主题状态变化历史
- 观察者 + 单例:主题作为单例供全局访问
- 观察者 + 组合:观察者本身可以是组合结构
典型应用场景
- GUI事件处理(如按钮点击事件)
- 股票价格变动通知
- 气象站数据发布
- 社交媒体订阅/推送
- 游戏中的成就系统
- MVC架构中的模型-视图同步
代码示例(推模型实现)
java
// 主题接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 观察者接口
interface Observer {
void update(float temperature, float humidity);
}
// 具体主题
class WeatherData implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
public void setMeasurements(float temperature, float humidity) {
this.temperature = temperature;
this.humidity = humidity;
notifyObservers();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer o : observers) {
o.update(temperature, humidity);
}
}
}
// 具体观察者
class CurrentConditionsDisplay implements Observer {
@Override
public void update(float temperature, float humidity) {
System.out.println("Current conditions: " + temperature + "°C and " + humidity + "% humidity");
}
}