观察者模式-Observer Pattern
序言
我们在手机上关注了一个城市的天气:天气一变,手机 App 立刻弹出通知。我们并没有天天去刷新天气页面,而是"订阅"了天气服务,服务变化时主动推送给我们。订阅 → 通知 → 响应,这就是观察者模式的现实模型。
观察者模式(Observer Pattern)又被称为发布-订阅模式(Publish-Subscribe)的核心思想:当一个对象(主题)的状态发生变化时,自动通知所有关心它的对象(观察者)。
这种模式在现代软件中无处不在:微信公众号更新后推送给粉丝、股票 App 推送行情变化、UI 框架中按钮点击事件通知监听器……本质上都是观察者模式。
定义
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更 新。
三个关键词:
- 一对多:一个主题可以同时被多个观察者关注;
- 状态变化:主题只需要在状态变化时发出通知,不需要关心谁在听;
- 自动更新:观察者收到通知后自行处理,无需主题调用具体逻辑。
结构
让我们从一个问题开始
假设我们要开发一个天气预报系统:气象站(WeatherStation)测量到新的天气数据后,手机 App 和网页需要同时更新显示。
最直接的实现是让气象站直接调用每个展示端的更新方法:
public class WeatherStation {
private WeatherData data;
private PhoneApp phoneApp;
private WebApp webApp;
public void setData(WeatherData newData) {
this.data = newData;
// 直接调用每个展示端
phoneApp.update(data);
webApp.update(data);
}
}
问题显而易见:
- 气象站认识所有展示端,新增 一个展示端(比如大屏显示器)就要修改气象站代码;
- 气象站和展示端强耦合,违反开闭原则;
- 如果有些展示端暂时不需要更新,气象站还得管理开关逻辑。
观察者模式的结构
把"展示端"抽象成观察者接口,气象站只面向接口编程:
各个角色的职责如下:
- Subject(主题/被观察者):持有观察者列表,提供注册(
attach)、注销(detach)和通知(notifyObservers)方法,对应图中的WeatherStation。 - Observer(观察者):定义接收通知的接口,通常只有一个
update()方法,对应图中的Observer。 - ConcreteObserver(具体观察者):实现观察者接口,收到通知后执行自己的更新逻辑,对应图中的
PhoneApp、WebApp。
关键设计:主题只依赖 Observer 接口,不知道任何具体观察者的细节。新增观察者只需要实现接口并 attach,主题一行代码都不用改。
代码实现
我们来实现天气推送系统。
定义观察者接口
public interface Observer {
void update(WeatherData data);
}
定义天气数据
public class WeatherData {
private final double temperature;
private final double humidity;
private final double pressure;
public WeatherData(double temperature, double humidity, double pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
}
public double getTemperature() {
return temperature;
}
public double getHumidity() {
return humidity;
}
public double getPressure() {
return pressure;
}
}
定义主题(气象站)
import java.util.ArrayList;
import java.util.List;
public class WeatherStation implements Subject {
private final List<Observer> observers = new ArrayList<>();
private WeatherData data;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(data);
}
}
// 气象数据更新:新数据到达后通知所有观察者
public void setData(WeatherData newData) {
this.data = newData;
notifyObservers();
}
}
定义具体观察者
public class PhoneApp implements Observer {
private final String name;
public PhoneApp(String name) {
this.name = name;
}
@Override
public void update(WeatherData data) {
System.out.println(name + " 推送通知:温度 " + data.getTemperature()
+ "℃,湿度 " + data.getHumidity() + "%,气压 " + data.getPressure());
}
}
public class WebApp implements Observer {
@Override
public void update(WeatherData data) {
System.out.println("【网页】刷新天气面板:"
+ data.getTemperature() + "℃ / " + data.getHumidity() + "%");
}
}
客户端使用
public class Main {
public static void main(String[] args) {
// 主题:气象站
WeatherStation station = new WeatherStation();
// 观察者:手机 App、网页
PhoneApp phoneApp = new PhoneApp("我的手机");
WebApp webApp = new WebApp();
// 订阅
station.attach(phoneApp);
station.attach(webApp);
// 气象站更新数据,所有观察者自动收到通知
station.setData(new WeatherData(26.5, 60, 1013));
System.out.println("=========================");
// 新观察者加入
PhoneApp anotherPhone = new PhoneApp("家人的手机");
station.attach(anotherPhone);
station.setData(new WeatherData(27.0, 58, 1012));
System.out.println("=========================");
// 退订
station.detach(webApp);
station.setData(new WeatherData(27.5, 55, 1011));
}
}
输出结果:
我的手机 推送通知:温度 26.5℃,湿度 60.0%,气压 1013.0
【网页】刷新天气面板:26.5℃ / 60.0%
=========================
我的手机 推送通知:温度 27.0℃,湿度 58.0%,气压 1012.0
【网页】刷新天气面板:27.0℃ / 58.0%
家人的手机 推送通知:温度 27.0℃,湿度 58.0%,气压 1012.0
=========================
我的手机 推送通知:温度 27.5℃,湿度 55.0%,气压 1011.0
家人的手机 推送通知:温度 27.5℃,湿度 55.0%,气压 1011.0
核心收益:
- 气象站完全不知道有谁在监听,新增/移除观察者不影响气象站代码;
- 观察者各自处理自己的逻辑,互不干扰;
- 支持动态订阅与退订,运行时自由组合。
推模型与拉模型
观察者模式有两种常见变体:
- 推模型:主题在通知时把数据作为参数传给观察者(上面的例子就是推模型)。优点是观察者拿到的数据完整,缺点是不需要的观察者也会收到所有数据。
- 拉模型:主题只通知"数据变化了",观察者自己从主题获取感兴趣的数据。优点是按需获取,缺点是观察者需要持有主题引用。
实际框架中两者都有应用,可根据"数据量大小"和"观察者需求差异"选择。
在开源框架中的应用
Spring 事件机制:从源码看观察者模式
Spring 的事件机制是观察者模式在 Java 生态中最典型的应用。从源码层面看,spring-context 中每一个角色都与观察者模式一一对应:
- Observer(观察者) =
ApplicationListener接口; - Subject(主题) =
ApplicationEventPublisher/ApplicationEventMulticaster; - Event(事件对象) =
ApplicationEvent。
观察者接口:ApplicationListener
// org.springframework.context.ApplicationListener(源码)
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
// 观察者的回调:收到事件后执行
void onApplicationEvent(E event);
}
Spring 里所有监听器(无论用 implements ApplicationListener 还是 @EventListener 注解)最终都被适配为这个接口的实例——注解方法通过 ApplicationListenerMethodAdapter 包装成 ApplicationListener。
主题接口:ApplicationEventPublisher
// org.springframework.context.ApplicationEventPublisher(源码)
@FunctionalInterface
public interface ApplicationEventPublisher {
// 发布事件:所有订阅的监听器都会收到通知
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
广播核心:SimpleApplicationEventMulticaster
事件发布后,最终由 SimpleApplicationEventMulticaster 完成"一对多"通知。它的 multicastEvent 方法——获取该事件的所有监听器,逐个调用:
// org.springframework.context.event.SimpleApplicationEventMulticaster(源码骨架)
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 获取监听该事件类型的所有观察者(ApplicationListener)
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
// 异步模式:提交线程池执行
executor.execute(() -> invokeListener(listener, event));
} else {
// 同步模式:直接调用
invokeListener(listener, event);
}
}
}
通知调用链:invokeListener → doInvokeListener
// org.springframework.context.event.AbstractApplicationEventMulticaster(源码骨架)
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
} catch (Throwable ex) {
errorHandler.handleError(ex);
}
} else {
doInvokeListener(listener, event);
}
}
// 最终调用观察者的回调方法:这就是"自动更新"的落点
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
listener.onApplicationEvent(event); // <-- 观察者模式的 update()
}
与观察者模式结构的对应:
- 主题(
ApplicationEventPublisher)只面向ApplicationListener接口,不知道任何具体监听器; - 新增监听器 = 新增一个
ApplicationListener实现并注册到容器,发布方零改动; @TransactionalEventListener(事务提交后再通知)、@Async(异步执行)只是对"通知 时机/方式"的扩展,观察者模式的核心骨架不变。
JDK:java.util.Observable 源码
Java 早期在 java.util 包提供了观察者模式的标准实现。Observable 的源码本身就是一套完整的"主题"实现:
// java.util.Observable(源码)
public class Observable {
private boolean changed = false; // 状态变化标记
private Vector<Observer> obs; // 观察者列表
// 注册观察者
public synchronized void addObserver(Observer o) {
if (o == null) throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
// 通知观察者(无参数版本)
public void notifyObservers() {
notifyObservers(null);
}
// 通知观察者(带参数):把观察者列表复制出来,逐个调用 update()
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed) {
return; // 状态没有变化则不通知
}
arrLocal = obs.toArray();
clearChanged(); // 通知后清除变化标记
}
for (int i = arrLocal.length - 1; i >= 0; i--) {
((Observer) arrLocal[i]).update(this, arg); // 逐个通知观察者
}
}
// 标记状态已变化(protected:只有被观察者自己可以标记)
protected synchronized void setChanged() {
changed = true;
}
// 清除变化标记
protected synchronized void clearChanged() {
changed = false;
}
}
// java.util.Observer(源码:观察者接口)
public interface Observer {
// 状态变化时的回调
void update(Observable o, Object arg);
}
这段源码体现了观察者模式的几个关键设计:
setChanged()是 protected——只有被观察者自己可以标记"状态变了",外部无法伪造通知;notifyObservers先复制列表再遍历——避免在通知过程中修改列表导致并发问题;- 观察者列表用
Vector保存,注册/注销通过addObserver/deleteObserver完成。
由于 Observable 是类而不是接口(强制继承)、且不支持泛型和并发控制等缺陷,Java 9 起它已被标记为废弃(@Deprecated),官方推荐用 PropertyChangeListener 等替代方案。但它作为观察者模式的标准教材实现,仍是理解该模式的最佳源码样本。
适用场景
- 当一个对象的状态变化需要触发其他多个对象的更新,且触发关系需要动态增删时,可以使用观察者模式。
- 当希望实现广播式通知(一处的变化通知多方)且通知方不需要知道接收方是谁时,观察者模式天然契合。
- 在事件驱动架构、消息推送、UI 事件监听、日志告警等场景中,观察者模式是最基础的设计。
- 注意:观察者被通知后如果还要反过来影响主题,容易形成循环通知;大量观察者同步通知也可能影响性能,此时可考虑异步通知或线程池分发。
与其他设计模式的关系
- **中介者模式(Mediator)**与观察者容易混淆:中介者协调的是"多个对象之间的交互",观察者处理的是"一对多的状态通知"。可以组合使用:中介者内部用观察者实现广播。
- 主题维护观察者列表的增删、通知遍历逻辑,可以配合**迭代器模式(Iterator)**安全遍历观察者集合。
- 观察者的注册与通知往往与**命令模式(Command)结合(事件对象本质上是"通知命令"),与责任链模式(Chain of Responsibility)**结合可以实现事件的链式处理。
- 在 MVC 架构中,Model 变化通知 View 更新,就是观察者模式的经典应用。
总结
观察者模式建立了一条"一对多"的松耦合通知链路:
- 主题只依赖抽象的观察者接口,不知道任何具体观察者;
- 观察者动态注册/注销,运行时自由组合;
- 状态变化自动广播,观察者各自响应,互不干扰。
观察者模式的缺点是:通知是异步不可控的(观察者更新顺序不保证)、可能出现循环通知、观察者过多时通知性能下降。但这些都可以通过异步化、消息队列等方式缓解。观察者模式作为发布-订阅思想的基础,是现代事件驱动架构(Spring 事件、消息中间件、前端状态管理)的理论基石。