问题

什么是观察者模式?

答案

1. 核心概念

观察者模式(Observer Pattern) 是一种行为型设计模式,定义对象之间的一对多依赖关系,当一个对象(主题/被观察者)的状态发生改变时,所有依赖它的对象(观察者)都会得到通知并自动更新。

别名:发布-订阅模式(Publish-Subscribe Pattern)

核心思想

  • 主题(Subject):维护观察者列表,状态变化时通知所有观察者
  • 观察者(Observer):定义更新接口,接收主题的状态变化通知

典型应用场景

  • GUI 事件监听(按钮点击、鼠标移动)
  • Spring 事件机制(ApplicationEvent)
  • 消息队列(RabbitMQ、Kafka)
  • 数据绑定(Vue、React 的响应式更新)
  • 日志监控、指标统计

2. 实现原理与代码示例

基础实现:气象站数据发布

// 观察者接口
interface Observer {
    void update(float temperature, float humidity, float pressure);
}

// 主题接口
interface Subject {
    void registerObserver(Observer observer);   // 注册观察者
    void removeObserver(Observer observer);     // 移除观察者
    void notifyObservers();                     // 通知所有观察者
}

// 具体主题:气象站
class WeatherStation implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private float temperature;
    private float humidity;
    private float pressure;

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

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    // 状态改变时触发通知
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers(); // 通知所有观察者
    }
}

// 具体观察者1:当前天气显示
class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;

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

    private void display() {
        System.out.println("当前天气:温度 " + temperature + "°C,湿度 " + humidity + "%");
    }
}

// 具体观察者2:天气统计
class StatisticsDisplay implements Observer {
    private List<Float> temperatureHistory = new ArrayList<>();

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

    private void display() {
        float avg = (float) temperatureHistory.stream()
                .mapToDouble(Float::doubleValue)
                .average()
                .orElse(0.0);
        System.out.println("平均温度:" + String.format("%.1f", avg) + "°C");
    }
}

// 具体观察者3:天气预报
class ForecastDisplay implements Observer {
    private float currentPressure = 1013.0f;
    private float lastPressure;

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

    private void display() {
        if (currentPressure > lastPressure) {
            System.out.println("天气预报:天气转好");
        } else if (currentPressure < lastPressure) {
            System.out.println("天气预报:天气转差");
        } else {
            System.out.println("天气预报:天气稳定");
        }
    }
}

// 使用示例
public class Client {
    public static void main(String[] args) {
        // 创建主题
        WeatherStation weatherStation = new WeatherStation();

        // 创建观察者并注册
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay();
        ForecastDisplay forecastDisplay = new ForecastDisplay();

        weatherStation.registerObserver(currentDisplay);
        weatherStation.registerObserver(statisticsDisplay);
        weatherStation.registerObserver(forecastDisplay);

        // 更新天气数据(自动通知所有观察者)
        System.out.println("===== 第一次更新 =====");
        weatherStation.setMeasurements(25.5f, 65.0f, 1013.2f);

        System.out.println("\n===== 第二次更新 =====");
        weatherStation.setMeasurements(27.8f, 70.0f, 1012.5f);

        System.out.println("\n===== 移除统计显示后第三次更新 =====");
        weatherStation.removeObserver(statisticsDisplay);
        weatherStation.setMeasurements(23.2f, 60.0f, 1014.0f);
    }
}

输出结果

===== 第一次更新 =====
当前天气:温度 25.5°C,湿度 65.0%
平均温度:25.5°C
天气预报:天气转好

===== 第二次更新 =====
当前天气:温度 27.8°C,湿度 70.0%
平均温度:26.6°C
天气预报:天气转差

===== 移除统计显示后第三次更新 =====
当前天气:温度 23.2°C,湿度 60.0%
天气预报:天气转好

3. 推模型 vs 拉模型

推模型(Push Model)

主题主动将所有数据推送给观察者(上述示例采用推模型)。

// 推模型:主题推送所有数据
void update(float temperature, float humidity, float pressure);

特点

  • ✅ 简单直接
  • ❌ 观察者可能接收到不需要的数据
  • ❌ 主题和观察者耦合度较高

拉模型(Pull Model)

主题只通知观察者状态已改变,观察者自己拉取需要的数据。

// 观察者接口
interface Observer {
    void update(Subject subject); // 传递主题引用
}

// 具体观察者
class CurrentConditionsDisplay implements Observer {
    @Override
    public void update(Subject subject) {
        WeatherStation station = (WeatherStation) subject;
        // 按需拉取数据
        float temperature = station.getTemperature();
        float humidity = station.getHumidity();
        display(temperature, humidity);
    }
}

特点

  • ✅ 观察者按需获取数据,灵活性高
  • ✅ 降低耦合度
  • ❌ 观察者需要知道主题的具体类型

4. JDK 内置的观察者模式

import java.util.Observable;
import java.util.Observer;

// 主题(JDK 自带)
class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;

        setChanged(); // 标记状态已改变
        notifyObservers(); // 通知观察者
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

// 观察者(JDK 自带)
class CurrentDisplay implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            WeatherData data = (WeatherData) o;
            System.out.println("温度:" + data.getTemperature() + "°C");
        }
    }
}

// 使用
WeatherData weatherData = new WeatherData();
CurrentDisplay display = new CurrentDisplay();
weatherData.addObserver(display);
weatherData.setMeasurements(25.0f, 60.0f, 1013.0f);

注意:JDK 的 Observable 和 Observer 在 Java 9 中被标记为 @Deprecated,不推荐使用,原因:

  • Observable 是类而非接口,限制了灵活性
  • 违反单一职责原则(Observable 既是主题又有状态管理)
  • 线程不安全(setChanged 和 notifyObservers 未同步)

5. Spring 事件机制(观察者模式的应用)

// 自定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private String orderId;

    public OrderCreatedEvent(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }

    public String getOrderId() {
        return orderId;
    }
}

// 事件监听器(观察者)
@Component
public class EmailNotificationListener {

    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("发送邮件通知:订单 " + event.getOrderId() + " 已创建");
    }
}

@Component
public class InventoryListener {

    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        System.out.println("扣减库存:订单 " + event.getOrderId());
    }
}

// 事件发布者(主题)
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void createOrder(String orderId) {
        // 创建订单业务逻辑
        System.out.println("创建订单:" + orderId);

        // 发布事件
        publisher.publishEvent(new OrderCreatedEvent(this, orderId));
    }
}

执行流程

创建订单:ORDER-001
发送邮件通知:订单 ORDER-001 已创建
扣减库存:订单 ORDER-001

Spring 事件机制的优势

  • ✅ 解耦业务逻辑(订单创建与邮件、库存解耦)
  • ✅ 易于扩展(新增监听器无需修改发布者)
  • ✅ 支持异步事件(@Async + @EventListener)
  • ✅ 支持事件顺序控制(@Order 注解)

6. 异步观察者模式

@Component
public class AsyncEventListener {

    @Async
    @EventListener
    public void handleOrderCreatedAsync(OrderCreatedEvent event) {
        System.out.println("异步处理订单:" + event.getOrderId() +
                           " (线程:" + Thread.currentThread().getName() + ")");
    }
}

// 启用异步支持
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-event-");
        executor.initialize();
        return executor;
    }
}

7. 观察者模式 vs 发布订阅模式

对比维度 观察者模式 发布订阅模式
通信方式 观察者和主题直接通信 通过消息中间件间接通信
耦合度 观察者知道主题的存在 发布者和订阅者互不知晓
实现复杂度 简单(内存中) 复杂(需要消息中间件)
典型应用 Spring 事件、GUI 监听 消息队列(Kafka、RabbitMQ)

发布订阅模式示意

发布者 → 消息队列(Broker) → 订阅者

8. 优缺点分析

优点

  • ✅ 降低耦合度(主题和观察者松耦合)
  • ✅ 支持广播通信(一次通知多个观察者)
  • ✅ 符合开闭原则(新增观察者无需修改主题)
  • ✅ 动态订阅(运行时添加/移除观察者)

缺点

  • ❌ 观察者过多时通知耗时(同步通知阻塞)
  • ❌ 循环依赖风险(观察者触发主题状态变化)
  • ❌ 内存泄漏风险(忘记移除观察者)
  • ❌ 通知顺序不可控(除非特殊处理)

9. 实际开发中的注意事项

防止内存泄漏

// 务必在不需要时移除观察者
subject.removeObserver(observer);

// Spring 中使用弱引用监听器(自动回收)
@EventListener
public void handleEvent(MyEvent event) { ... }

线程安全问题

// 使用线程安全的集合
private List<Observer> observers = new CopyOnWriteArrayList<>();

// 或者在通知时加锁
public synchronized void notifyObservers() { ... }

避免循环通知

// 使用标志位防止循环
private boolean isNotifying = false;

public void notifyObservers() {
    if (isNotifying) return;
    isNotifying = true;
    try {
        // 通知逻辑
    } finally {
        isNotifying = false;
    }
}

10. 答题总结

简洁回答模板

“观察者模式定义一对多的依赖关系,当主题状态改变时,所有观察者自动收到通知并更新。

核心结构

  1. 主题(Subject):维护观察者列表,状态变化时通知观察者
  2. 观察者(Observer):定义更新接口,接收通知并作出响应

两种模型

  • 推模型:主题推送所有数据给观察者(简单但耦合高)
  • 拉模型:观察者主动拉取需要的数据(灵活但需要知道主题类型)

典型应用

  • Spring 事件机制(ApplicationEvent + @EventListener)
  • GUI 事件监听(按钮点击、数据绑定)
  • 消息队列(Kafka、RabbitMQ 的发布订阅模式)

与发布订阅的区别:观察者模式是同步直接通信,发布订阅通过消息中间件异步解耦。

适用于对象间存在一对多依赖,状态变化需要通知多个对象的场景,能有效解耦并支持动态订阅。”