什么是观察者模式?应用场景有哪些?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官提出这个问题,通常旨在考察以下几个层面:

  1. 对设计模式的理解深度:你是否能清晰、准确地描述一种经典设计模式的核心思想与结构,而非仅仅停留在名称上。
  2. 建模与抽象能力:你是否能将 “一个对象状态变化,其他依赖对象需要得到通知” 这一常见场景,抽象为标准的对象间交互模型。
  3. 实际应用经验:你是否能在掌握理论的基础上,列举出在真实项目、主流框架或系统中该模式的应用场景,这能反映你的知识迁移能力和工程视野。
  4. 对松耦合设计的认识:面试官不仅想知道模式是什么,更是想考察你是否理解它在 “解耦” 方面的巨大价值,以及如何通过它来构建更灵活、可维护的系统。

核心答案

观察者模式(Observer Pattern), 也称为发布-订阅模式, 是一种行为型设计模式。它定义了一种一对多的依赖关系, 让多个观察者对象同时监听某一个主题对象。当这个主题对象的状态发生变化时, 会自动通知所有依赖于它的观察者对象, 使它们能够自动更新。

其核心包含两个角色:

  • Subject: 主题/被观察者。它维护一个观察者列表, 提供添加、删除观察者的方法, 以及一个通知所有观察者的方法。
  • Observer: 观察者。它定义一个更新接口, 用于在接收到主题通知时进行自我更新。

深度解析

原理/机制

观察者模式的核心机制在于回调依赖管理。主题对象并不需要知道具体是哪些对象在关注它, 它只依赖于一个抽象的观察者接口。当自身状态变更时, 它通过遍历其维护的观察者列表, 调用每个观察者接口中定义的更新方法(如 update()), 从而将变更 “推送” 出去。这个过程实现了 “对象间的动态、松耦合联动”

代码示例

以下是一个极简的实现, 模拟气象站数据更新时, 多个展示面板(布告板)需要实时刷新的场景。

// 1. 主题接口
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

// 2. 观察者接口
interface Observer {
    void update(float temperature, 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 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;
        measurementsChanged(); // 数据变化,触发通知
    }

    private void measurementsChanged() {
        notifyObservers();
    }
}

// 4. 具体观察者:当前状况展示布告板
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);
        // 可以轻松添加更多观察者,如StatisticsDisplay, ForecastDisplay

        // 模拟新数据到达
        weatherData.setMeasurements(26.5f, 65.0f, 1013.1f);
        weatherData.setMeasurements(27.8f, 70.0f, 1012.5f);
    }
}

对比分析与注意事项

  • 推模型 vs 拉模型
    • 推模型(如上例):主题将详细的变更数据作为参数传递给观察者。简单直接,但可能传递观察者不需要的信息。
    • 拉模型:主题在通知时只传递自身的引用(this), 观察者根据需要主动从主题中 “拉取” 所需数据。更灵活,但观察者需要知道主题的结构。
    • Java 内置的 java.util.Observable(已过时)和 java.util.Observer 就支持拉模型。
  • 最佳实践
    1. 抽象主题与观察者:始终依赖接口,这是实现松耦合的关键。
    2. 注意线程安全:在并发环境下,对观察者列表的增删改查需要同步,或者使用线程安全的集合(如 CopyOnWriteArrayList)。
    3. 防止内存泄漏:观察者对象在不再需要时,务必从主题中注销(removeObserver), 否则会因其被主题列表强引用而无法被垃圾回收。
  • 常见误区
    • 滥用与过度设计:不是所有状态变化通知都需要用观察者模式。对于简单的回调,直接使用函数式接口(如 Consumer)或监听器接口可能更轻量。
    • 混淆事件与状态:观察者模式更侧重于对象状态变化的传播。对于更复杂的事件驱动架构,如Spring ApplicationEvent或消息队列,其思想同源,但实现和功能更强大。

应用场景

观察者模式在真实世界中无处不在:

  1. GUI 事件监听:Java Swing/AWT、Android 中的各种 OnClickListener 都是典型的观察者模式。
  2. 消息队列/事件总线:这是观察者模式的分布式和异步扩展。生产者发布消息到主题(Topic), 多个消费者订阅该主题并独立处理消息。如 Kafka、RabbitMQ 的核心模型。
  3. Spring 框架事件机制ApplicationContext 通过 ApplicationEventApplicationListener 接口实现了事件发布与监听, 用于解耦业务模块(如订单创建后触发发短信、扣库存等操作)。
  4. 配置中心动态刷新:如 Apollo、Nacos。当配置在服务端修改后, 所有订阅了该配置的客户端应用会实时收到通知并更新本地配置。
  5. Reactive 编程:响应式流(如 Project Reactor、RxJava)中的 Flux/Observable 就是被观察者, 而 subscribe 的订阅者就是观察者。

总结

观察者模式通过抽象依赖回调机制, 实现了主题与观察者之间的松耦合联动, 是应对对象间一对多动态依赖关系的经典解决方案, 在 GUI、事件驱动、消息通信等众多领域有广泛应用。