一、模式定义
观察者模式 属于行为型设计模式 ,用于建立对象间的一对多依赖关系。当主题(Subject)状态变化时,所有依赖的观察者(Observer)会自动收到通知并更新。
二、核心角色
- Subject(主题)
- 维护观察者列表,提供添加/删除观察者的方法
- 定义通知观察者的方法
- Observer(观察者接口)
- 定义更新接口,用于接收主题通知
- ConcreteSubject(具体主题)
- 存储具体状态信息
- 状态改变时触发通知
- ConcreteObserver(具体观察者)
- 实现更新逻辑,保持与主题状态同步
三、经典实现(以气象站为例)
登录后复制
plain
// 1. 主题接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 2. 观察者接口
interface Observer {
void update(float temp, float humidity, float pressure);
}
// 3. 具体主题实现
class WeatherData implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
@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, pressure);
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
// 4. 具体观察者实现
class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Subject weatherData) {
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature
+ "°C and " + humidity + "% humidity");
}
}
// 5. 使用示例
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay display = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(25.5f, 65, 1013.1f);
weatherData.setMeasurements(26.8f, 70, 1012.5f);
}
}
四、两种通知模型对比
|--------|---------------|--------------------|
| 特性 | 推模型(Push) | 拉模型(Pull) |
| 数据传递方式 | 主题主动发送完整数据 | 观察者从主题拉取所需数据 |
| 实现复杂度 | 观察者可能收到不需要的数据 | 观察者按需获取数据,需要维护主题引用 |
| 耦合度 | 观察者依赖具体数据结构 | 观察者只需知道主题存在 |
| 性能考量 | 可能传输冗余数据 | 需要多次调用获取方法 |
拉模型改进示例:
登录后复制
plain
interface Subject {
// 新增获取状态的方法
float getTemperature();
float getHumidity();
float getPressure();
}
class WeatherData implements Subject {
// 保持原有接口不变
// 新增状态获取方法...
}
class CurrentConditionsDisplay implements Observer {
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update() {
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
五、Java内置支持
登录后复制
plain
// 已过时,不推荐使用,仅作了解
import java.util.Observable;
import java.util.Observer;
class WeatherData extends Observable {
public void measurementsChanged() {
setChanged(); // 必须调用
notifyObservers(); // 无参为拉模型
// notifyObservers(data); // 带参为推模型
}
}
class Display implements Observer {
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
// 拉取数据
WeatherData wd = (WeatherData) o;
// 或使用arg参数获取推送数据
}
}
}
六、模式优劣分析
优势:
- 符合开闭原则:新增观察者无需修改主题
- 运行时动态建立对象关系
- 实现抽象耦合,主题无需知道具体观察者
劣势:
- 通知顺序不可控
- 频繁更新可能影响性能
- 循环依赖风险
七、应用场景
- 跨系统事件通知(如订单状态更新)
- GUI事件处理(按钮点击监听)
- 实时数据监控(股票价格变动)
- 游戏中的成就系统解锁
- 分布式配置中心(配置变更通知)
八、高级应用技巧
- 异步观察者 :
使用线程池处理通知,避免阻塞主题线程
登录后复制
plain
ExecutorService executor = Executors.newCachedThreadPool();
void notifyObservers() {
for (Observer o : observers) {
executor.execute(() -> o.update(...));
}
}
- 防止失效监听 :
使用弱引用(WeakReference)存储观察者,防止内存泄漏 - 保证通知顺序 :
使用PriorityQueue实现带优先级的观察者队列 - 跨进程观察者 :
结合消息队列(如RabbitMQ、Kafka)实现分布式观察者模式
九、相关模式对比
- 中介者模式 vs 观察者模式
中介者集中处理对象间通信,而观察者建立直接订阅关系 - 发布-订阅模式 vs 观察者模式
发布-订阅通过消息代理解耦,观察者是直接通信
十、最佳实践建议
- 优先使用拉模型,降低耦合度
- 为观察者接口设计合适的更新粒度
- 考虑使用CopyOnWriteArrayList保证线程安全
- 对于复杂场景,建议使用Guava的EventBus或Spring事件机制