观察者模式:发布-订阅与事件驱动

FreeGuideOnline 最新 2026-06-18

什么是观察者模式

观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这种模式是事件驱动架构和发布-订阅模型的核心思想,广泛应用于图形界面、消息系统、异步编程等场景。

简单来说,观察者模式里有两种角色:

  • 主题(Subject):被观察的对象,维护一个观察者列表,提供添加、删除和通知观察者的方法。
  • 观察者(Observer):定义一个更新接口,当主题状态变化时,主题会调用所有观察者的更新方法。

模式结构

一个标准的观察者模式包含以下组件:

  • Subject(抽象主题):提供注册、移除观察者的接口,以及通知所有观察者的方法。
  • ConcreteSubject(具体主题):存储状态,当状态改变时触发通知。
  • Observer(抽象观察者):定义 update() 方法,供主题调用。
  • ConcreteObserver(具体观察者):实现 update(),保持对具体主题的引用,并同步自身状态。

这些组件之间的关系可以用下面的图示表示:

+----------------+          +---------------------+
|   Subject      |          | <<interface>>       |
|----------------|          |    Observer         |
| - observers[]  |<>------->|---------------------|
| + attach(o)    |          | + update(subject)   |
| + detach(o)    |          +---------------------+
| + notify()     |                   ^
+----------------+                   |
         ^                           |
         |                           |
+----------------+          +---------------------+
| ConcreteSubject|          | ConcreteObserver    |
| - state        |          | - observerState     |
| + getState()   |          | + update(subject)   |
| + setState()   |          +---------------------+
+----------------+

为什么需要观察者模式

在没有观察者模式的系统中,如果一个对象的状态变化需要通知多个其他对象,通常会使用轮询或直接调用依赖对象的方法。这种方式会导致:

  • 高耦合:主题必须知道所有依赖它的对象的具体类型。
  • 可维护性差:增加新的观察者需要修改主题代码,违反开闭原则。
  • 重复代码:每个需要通知的场景都要重复编写通知逻辑。

观察者模式将通知机制抽象出来,使主题和观察者之间只依赖抽象接口,从而实现松耦合。主题可以随时添加新的观察者而不需修改自身代码,观察者也可以独立变化。

代码实现示例

下面用 Java 演示一个简单的天气站和显示板示例,展示观察者模式的标准实现。

1. 定义观察者接口

public interface Observer {
    void update(float temperature, float humidity, float pressure);
}

2. 定义主题接口

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

3. 具体主题(天气数据)

import java.util.ArrayList;

public class WeatherData implements Subject {
    private ArrayList<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    @Override
    public 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();
    }
}

4. 具体观察者(显示设备)

public class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Subject weatherData) {
        weatherData.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }

    public void display() {
        System.out.println("当前状况: " + temperature
                + "°C 湿度 " + humidity + "%");
    }
}

5. 测试运行

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentDisplay = 
            new CurrentConditionsDisplay(weatherData);

        weatherData.setMeasurements(26.6f, 65f, 1013.1f);
        weatherData.setMeasurements(27.3f, 70f, 1012.5f);
    }
}

执行后,每次调用 setMeasurements 都会自动通知所有注册的观察者,当前状况显示板会实时更新。

观察者模式与发布-订阅模式的关系

许多人把观察者模式和发布-订阅模式(Pub/Sub)当作同一个概念,实际上它们有细微区别:

维度 观察者模式 发布-订阅模式
耦合方式 主题和观察者彼此知道对方(松耦合) 发布者和订阅者完全不知道对方(解耦)
通信渠道 主题直接调用观察者的方法 通过消息代理或事件总线进行通信
应用场景 组件内部或小型系统中 分布式系统、跨进程通信、消息队列
实现复杂度 较低 较高(需要中间件支持)

观察者模式是实现发布-订阅的基础。在 JavaScript 前端框架(如 Vue、React)或 Node.js 的事件发射器中,都是观察者模式或发布-订阅思想的应用。当你要在组件间通信,但不想让它们直接依赖时,可以引入事件总线(发布-订阅的变体)。

实际应用场景

观察者模式在软件开发中无处不在:

  • GUI 事件处理:按钮点击时,所有注册的监听器被调用。
  • MVC 架构:模型作为主题,视图作为观察者,当模型数据变化时,视图自动刷新。
  • 数据绑定:前端框架中的数据双向绑定本质是观察者模式。
  • 消息系统:如 Java 的 java.util.Observer(已弃用,但思想延续),JavaFX 的 PropertyListener,Android 广播等。
  • 游戏开发:成就系统监听玩家行为,当某个条件达成时触发奖励。
  • 实时数据推送:股票报价应用,服务器端作为主题,客户端作为观察者。

观察者模式的优缺点

优点

  • 松耦合:主题只需要知道观察者的抽象接口,具体观察者可以动态增减。
  • 支持广播通信:一个主题可以同时通知所有订阅者,无需修改主题。
  • 符合开闭原则:增加新的观察者不需要修改主题。

缺点

  • 通知顺序不可控:如果多个观察者之间有依赖,可能会导致意料之外的结果。
  • 可能引起性能问题:观察者太多或某个观察者处理时间过长,会拖慢通知过程。
  • 内存泄漏风险:如果观察者忘记从主题中注销,而观察者本身已被释放(如缺少强引用),会导致无法回收的内存泄漏。因此需在适当时候移除观察者(如组件销毁时)。

注意事项与最佳实践

  1. 使用弱引用或自动注销:为防止内存泄漏,可以在主题中使用 WeakReference 存储观察者,或提供明确的生命周期钩子(如 onDestroy)来移除观察者。

  2. 避免在通知过程中修改观察者列表:如果在 notifyObservers 遍历期间添加或移除观察者,可能导致 ConcurrentModificationException。可以先复制列表或使用线程安全的集合。

  3. 封装变化:使用抽象主题和抽象观察者,让系统更灵活。

  4. 区分推模型和拉模型

    • 推模型:主题将所有相关数据通过 update 方法直接推送给观察者。
    • 拉模型:主题只通知变化,观察者收到通知后自己从主题拉取需要的数据。 实际项目中,可以根据需要选择,推模型能减少观察者响应次数,但可能传递过多数据;拉模型使观察者按需获取,但增加了调用次数。
  5. 不要滥用:如果只有一对一的依赖关系,直接调用即可,不必引入观察者模式增加设计复杂度。

小结

观察者模式是事件驱动编程的基石。它让开发者能够构建松耦合、易扩展的系统。理解观察者模式有助于掌握现代前端框架的响应式原理、分布式消息系统的设计思想以及许多常见的设计模式变体(如发布-订阅模式)。当你在设计需要多个对象响应单一事件时,不妨考虑观察者模式 —— 它既简单又强大。