观察者模式

观察者模式(Observer Pattern)是 GoF 经典行为型设计模式 ,也被称为发布-订阅模式(Publish-Subscribe)。核心思想:定义一对多的依赖关系,让一个主题对象状态发生改变时,所有依赖它的观察者对象自动收到通知并更新

该模式完美实现事件驱动、状态联动、解耦通知,是Spring事件机制、消息队列、GUI事件监听、业务联动更新的底层核心,属于面试和开发高频模式。

核心口诀:主题统一发布,观察者被动订阅,状态一变,全员联动

一、观察者模式核心概述

1.1 核心定义

建立「主题-观察者」的一对多订阅机制:主题(被观察者)维护一组观察者列表,当自身数据、状态发生变更时,自动遍历所有观察者,主动推送更新通知,观察者接收消息后自主执行更新逻辑,全程无需主动轮询。

1.2 解决的核心痛点

常规业务中,多个业务模块依赖同一个核心数据状态,传统写法存在严重缺陷:

  • 硬编码耦合严重:核心业务需要手动调用多个依赖模块的更新方法,新增联动模块需修改核心代码,违背开闭原则

  • 代码冗余臃肿:大量重复的状态判断、主动调用逻辑,维护成本极高

  • 联动灵活性差:无法动态新增、移除、订阅、取消订阅观察者

  • 主动轮询消耗资源:通过定时轮询判断状态变更,效率低、实时性差

1.3 四大核心角色

观察者模式拥有固定的标准四层角色架构,职责划分清晰,所有实现均基于该结构拓展:

  • 抽象主题(Subject/Observable):被观察者顶层接口,定义订阅、取消订阅、通知更新的统一规范,负责管理所有观察者集合。

  • 具体主题(ConcreteSubject):具体被观察者,持有核心状态数据,状态变更时触发通知机制,主动推送消息给所有订阅的观察者。

  • 抽象观察者(Observer):观察者顶层接口,定义统一的更新方法,所有具体观察者遵循该规范。

  • 具体观察者(ConcreteObserver):实现抽象观察者接口,接收主题通知,执行自身专属的更新、联动业务逻辑。

1.4 标准结构说明

整体架构分为两层核心体系:主题体系、观察者体系,完全解耦分离,无硬编码依赖。

核心关联关系

  • 实现关系:具体主题实现抽象主题接口,所有具体观察者实现抽象观察者接口,保证行为统一、规范统一。

  • 聚合关系:抽象主题聚合持有多个抽象观察者,形成一对多的订阅关系,一个主题可绑定无数观察者。

  • 触发关系:主题状态变更后,主动回调观察者更新方法,观察者被动接收通知、执行逻辑。

  • 解耦关系:主题无需知晓观察者具体实现,仅依赖顶层接口,新增、删除观察者无需修改主题代码。

完整调用链路:业务触发主题状态变更 → 主题遍历所有已订阅观察者 → 批量推送更新通知 → 各观察者独立执行自身更新逻辑 → 完成全局联动。

1.5 核心设计思想

解耦联动、被动通知、动态订阅。将「状态变更」与「后续联动逻辑」彻底拆分,主题只负责状态管理与消息发布,观察者只负责自身业务更新,遵循单一职责与开闭原则。

二、传统硬编码联动弊端(反例对比)

以「订单状态变更联动」为例,订单完成后需要通知库存、消息、积分模块,传统写法耦合严重:

复制代码
public class OrderService {
    // 订单完成,硬编码联动所有模块
    public void finishOrder() {
        // 核心订单逻辑
        System.out.println("订单已完成");
        // 硬编码调用联动模块
        StockService.reduceStock();
        MessageService.sendNotice();
        IntegralService.addIntegral();
    }
}

缺陷:新增联动模块必须修改OrderService代码,违背开闭原则;模块高度耦合,无法动态增减联动逻辑,代码拓展性极差。

三、观察者模式标准代码实战

业务场景:订单状态变更为完成后,自动联动库存扣减、用户消息推送、会员积分增加,支持后续动态新增联动模块。

3.1 抽象观察者(统一更新规范)

复制代码
// 抽象观察者:统一更新接口
public interface Observer {
    // 接收主题通知,执行更新逻辑
    void update(String message);
}

3.2 具体观察者(各模块独立实现)

复制代码
// 具体观察者1:库存模块
public class StockObserver implements Observer {
    @Override
    public void update(String message) {
        System.out.println("库存模块接收通知:" + message + ",执行商品库存扣减逻辑");
    }
}

// 具体观察者2:消息推送模块
public class MessageObserver implements Observer {
    @Override
    public void update(String message) {
        System.out.println("消息模块接收通知:" + message + ",向用户推送订单完成提醒");
    }
}

// 具体观察者3:积分模块
public class IntegralObserver implements Observer {
    @Override
    public void update(String message) {
        System.out.println("积分模块接收通知:" + message + ",为用户新增消费积分");
    }
}

3.3 抽象主题(被观察者顶层规范)

复制代码
import java.util.List;

// 抽象主题(被观察者)
public interface Subject {
    // 订阅观察者
    void registerObserver(Observer observer);
    // 取消订阅
    void removeObserver(Observer observer);
    // 通知所有观察者更新
    void notifyObservers(String message);
}

3.4 具体主题(核心状态主体)

复制代码
import java.util.ArrayList;
import java.util.List;

// 具体主题:订单主题(被观察者)
public class OrderSubject implements Subject {
    // 维护所有订阅的观察者
    private final List<Observer> observerList = new ArrayList<>();

    // 新增订阅
    @Override
    public void registerObserver(Observer observer) {
        observerList.add(observer);
    }

    // 取消订阅
    @Override
    public void removeObserver(Observer observer) {
        observerList.remove(observer);
    }

    // 批量通知所有观察者
    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observerList) {
            observer.update(message);
        }
    }

    // 核心业务:订单完成,触发通知
    public void finishOrder() {
        System.out.println("订单核心逻辑:订单交易完成");
        // 状态变更,推送消息给所有观察者
        notifyObservers("订单状态更新为【已完成】");
    }
}

3.5 客户端测试调用

复制代码
public class ObserverTest {
    public static void main(String[] args) {
        // 1.创建核心主题(被观察者)
        OrderSubject orderSubject = new OrderSubject();

        // 2.注册多个观察者(订阅)
        orderSubject.registerObserver(new StockObserver());
        orderSubject.registerObserver(new MessageObserver());
        orderSubject.registerObserver(new IntegralObserver());

        // 3.触发主题状态变更,自动联动所有观察者
        orderSubject.finishOrder();
    }
}

3.6 拓展优势体现

后续需要新增「日志记录、优惠券返还」等联动模块,无需修改原有任何代码,只需新建对应观察者类并完成订阅即可,完全符合开闭原则。

四、JDK内置观察者实现(原生API)

JDK原生提供了观察者模式内置支持(java.util.Observable主题类、java.util.Observer观察者接口),可快速实现标准监听逻辑,适合快速开发场景。

注意:JDK内置API为老旧实现,不支持多线程、自定义扩展,生产级开发推荐自定义实现观察者模式。

五、优缺点深度分析

5.1 优点

  • 彻底解耦:主题与观察者完全解耦,互不依赖具体实现,仅依赖顶层接口

  • 完美遵循开闭原则:新增、删除联动观察者无需修改主题核心代码,拓展性极强

  • 动态订阅灵活:运行期可动态注册、取消订阅观察者,实时调整联动逻辑

  • 一对多自动联动:一次状态变更,批量触发所有关联业务,代码简洁无冗余

  • 事件驱动思想:摆脱主动轮询,被动通知响应,执行效率、实时性更高

5.2 缺点

  • 可能产生广播风暴:观察者数量过多时,批量通知会产生大量联动逻辑,影响性能

  • 通知顺序不可控:默认遍历顺序执行观察者逻辑,无法自定义执行优先级

  • 单次消息传递:默认统一推送消息,无法精准定向通知指定观察者

  • 循环依赖风险:多主题多观察者嵌套订阅,可能出现循环调用、死循环问题

六、适用场景与生产落地

6.1 核心适用场景

  • 一个数据状态变更,需要联动多个模块自动更新的业务场景

  • 需要动态新增、移除联动逻辑,频繁拓展订阅关系的场景

  • 需要实现事件驱动、消息推送、状态监听的业务场景

  • 替代主动轮询,提升系统实时性与执行效率的场景

6.2 经典落地场景

  • Spring事件机制:ApplicationListener 事件监听,基于观察者模式实现容器事件发布订阅

  • 消息中间件:MQ发布订阅模式,生产者发布消息、消费者监听消费

  • GUI界面事件:按钮点击、窗口监听等事件回调机制

  • 业务联动更新:订单状态、支付状态、库存状态变更后的多模块联动

  • 配置中心刷新:Nacos、Apollo配置变更,自动推送刷新客户端配置

七、高频面试核心考点

7.1 观察者模式核心本质

一对多事件订阅、状态联动自动通知、业务完全解耦。核心是实现「发布-订阅」机制,分离状态变更与联动逻辑。

7.2 观察者模式 vs 中介者模式

  • 观察者:一对多关系,主题统一发布,观察者被动接收,无中间调度层

  • 中介者:多对多解耦,所有对象通过中介者通信,彻底消除对象间相互依赖

7.3 自定义观察者与JDK内置观察者区别

  • JDK内置API简洁易用,但扩展性差、不支持多线程、无法定制化

  • 自定义观察者灵活度高,可自定义通知规则、执行顺序、异常处理,适配生产复杂场景

八、面试速记总结

观察者模式是行为型模式,核心为主题(被观察者)+ 观察者的一对多订阅架构。核心价值是实现状态变更的自动联动、业务模块彻底解耦,摆脱硬编码与主动轮询。支持动态订阅、灵活拓展,遵循开闭原则,是事件驱动、消息订阅、Spring事件机制的底层核心,生产中优先使用自定义实现,规避JDK原生API的局限性。

终极口诀:一物变动,万物监听,发布解耦,订阅联动。

相关推荐
野生技术架构师1 小时前
1000+ Java面试题知识图谱:从基础语法到分布式架构的完整拓扑
java·面试
步行cgn1 小时前
Spring 底层如何创建对象:反射机制与实例化策略
java·后端·spring
何以解忧,唯有..2 小时前
Redis 过期事件监听:原理与实战
java
风中的小熊生气2 小时前
Spring Boot 面试知识(二):分层、IoC、异常、配置与 Redis
java·springboot
泡茶喝茶写代码2 小时前
量化数据开发实战系列(第 25 篇):基金基础接口实战:基金列表、ETF-LOF 分类、基金概况、净值数据
java·数据库·人工智能·python·mysql
shehuiyuelaiyuehao3 小时前
算法46,分治快排,排序数组
java·算法·排序算法·排序
野生技术架构师3 小时前
1200 道 JAVA 面试题(各大企业常见面试题及答案)
java·开发语言
careathers3 小时前
【数据结构】栈
java·数据结构
邪修king3 小时前
Re:Linux系统篇(二十六):文件系统(二):Ext 文件系统底层详解:从 inode、块组到软硬链接,结合 Windows 讲透文件管理本质
android·java·linux