设计模式—观察者模式(超全工程面试+MQ关联详解)
本文目录如下:
1. 为什么需要观察者模式
1.1 观察者模式解决的核心痛点
1.2 典型使用场景
1.3 不适合观察者模式的场景
2. 观察者模式核心体系与角色
2.1 核心特点
2.2 四大标准角色
3. 观察者模式标准实战实现(推模型·工程最常用)
3.1 业务场景
3.2 抽象观察者
3.3 具体观察者(多业务联动)
3.4 抽象主题 & 具体主题
3.5 客户端调用
4 观察者模式标准实战实现(拉模型)
4.1 拉模型业务场景
4.2 拉模型核心代码实现
4.3 拉模型客户端测试
5. 推模型 vs 拉模型核心区别(面试必背)
5.1 推模型
5.2 拉模型
6. 观察者模式优缺点
6.1 优点
6.2 缺点(针对于上述案例)
7. 企业级成熟实现:SpringBoot异步事件监听(生产落地版)
7.1 业务场景
7.2 企业级落地优势(对比原生手写)
7.3 业务场景
6.4 第一步:定义订单事件(事件主体-承载数据)
7.5 第二步:定义多个观察者(事件监听器)
7.6 第三步:事件发布器(主题Subject)
7.7 第四步:开启异步(启动类注解)
7.8 执行结果 & 核心特性证明
8、企业级进阶:事务绑定事件(解决生产最大BUG)
9. 企业版 VS 手写基础版核心对比
10. 工程常见问题与解决方案
10.1 同步阻塞导致主接口RT高
10.2 事件丢失、异常雪崩
10.3 内存泄漏
10.4 执行顺序不可控
11. 重点拓展:观察者模式 与 消息队列MQ深度对比
11.1 核心关系:MQ本质就是「分布式观察者模式」
11.2 详细区别(工程落地必背)
11.3 落地选型规范(企业级标准)
12. 高频面试:易混模式区分
12.1 观察者模式 vs 策略模式
12.2 观察者模式 vs 中介者模式
13. 主流框架源码落地(面试高频)
13.1 JDK 原生Observer
13.2 Spring事件机制(经典落地)
13.3 注册器、监控、配置中心
14. 核心总结
1. 为什么需要观察者模式
当业务存在「一个行为触发多个后续动作」的联动场景,且后续需要频繁新增、删除联动业务时,使用观察者模式替代硬编码链式调用。
1.1 观察者模式解决的核心痛点
a. 业务联动硬编码耦合严重:主流程完成后,挨个调用后续业务方法,主流程依赖所有子业务,代码臃肿。
b. 新增联动业务需要修改主流程代码:每加一个后续动作,就要改动主方法,严重违反开闭原则。
c. 主流程阻塞严重、响应变慢:所有后续同步逻辑串行执行,主接口RT持续拉高。
d. 联动逻辑无法复用、无法独立管控:订阅逻辑写死在业务内部,无法动态注册、动态注销。
e. 事件分发与事件处理混为一谈:发布逻辑、消费逻辑耦合,无法单独拓展、降级、异步。
1.2 典型使用场景
a. 一对多业务联动场景:订单创建后触发:发短信、发推送、记录日志、更新库存、积分发放。
b. 状态变更自动通知场景:配置变更、热点数据更新、缓存刷新、状态流转推送。
c. 事件驱动型业务场景:消息监听、事件回调、流程钩子、业务后置处理。
d. 需要动态增减订阅者的场景:运行时动态注册、移除观察者,灵活拓展联动能力。
e. 主流程与子流程需要解耦的场景:主流程只负责核心业务,后置动作全部解耦异步处理。
1.3 不适合观察者模式的场景
a. 联动业务固定、永久不变:没有新增订阅需求,没必要抽象事件机制,属于过度设计。
b. 强事务一致性场景:观察者多为异步解耦,无法保证和主事务强一致。
c. 单一固定联动逻辑:只有一个后置动作,直接方法调用更简单。
d. 极高实时、强顺序依赖的链路:观察者异步无序,无法严格保证执行顺序。
2. 观察者模式核心体系与角色
2.1 核心特点
1.一对多依赖:一个主题对应多个观察者;
2.事件驱动解耦:发布者与订阅者无直接依赖;
3.动态订阅:运行时可自由注册、取消观察者;
4.推拉两种模型:推模型主动推送数据,拉模型由观察者主动拉取数据。
2.2 四大标准角色
抽象主题(Subject):维护观察者集合,提供订阅、取消订阅、通知抽象方法。
具体主题(Concrete Subject):状态持有者,状态变更后触发全局通知。
抽象观察者(Observer):统一更新接口,所有观察者统一行为规范。
具体观察者(Concrete Observer):接收事件,执行具体后置业务逻辑。
3. 观察者模式标准实战实现(推模型·工程最常用)
3.1 业务场景
订单创建成功后,自动触发:短信通知、积分发放、日志记录多个后置业务。
3.2 抽象观察者
// 抽象观察者:统一更新接口 public interface OrderObserver { void update(String orderNo); }3.3 具体观察者(多业务联动)
// 短信通知观察者 public class SmsObserver implements OrderObserver { @Override public void update(String orderNo) { System.out.println("【短信通知】订单"+orderNo+"发送下单成功短信"); } } // 积分发放观察者 public class ScoreObserver implements OrderObserver { @Override public void update(String orderNo) { System.out.println("【积分发放】订单"+orderNo+"发放消费积分"); } } // 日志记录观察者 public class LogObserver implements OrderObserver { @Override public void update(String orderNo) { System.out.println("【日志记录】订单"+orderNo+"记录下单操作日志"); } }3.4 抽象主题 & 具体主题
import java.util.ArrayList; import java.util.List; // 抽象主题 public abstract class OrderSubject { // 维护所有订阅的观察者 protected List<OrderObserver> observerList = new ArrayList<>(); // 订阅 public void attach(OrderObserver observer){ observerList.add(observer); } // 取消订阅 public void detach(OrderObserver observer){ observerList.remove(observer); } // 通知抽象方法 public abstract void notifyObserver(String orderNo); } // 具体主题:订单业务主体 public class OrderServiceImpl extends OrderSubject { // 核心业务:创建订单 public void createOrder(String orderNo){ System.out.println("【核心业务】订单创建成功:"+orderNo); // 状态变更,统一通知所有观察者 notifyObserver(orderNo); } @Override public void notifyObserver(String orderNo) { for (OrderObserver observer : observerList) { observer.update(orderNo); } } }3.5 客户端调用
public class ObserverClient { public static void main(String[] args) { // 1. 创建主题 OrderServiceImpl orderService = new OrderServiceImpl(); // 2. 注册所有观察者(可动态增减) orderService.attach(new SmsObserver()); orderService.attach(new ScoreObserver()); orderService.attach(new LogObserver()); // 3. 执行核心业务,自动触发所有联动 orderService.createOrder("ORD20260707001"); } }4 观察者模式标准实战实现(拉模型)
特点:主题只推送变更通知,不传递完整数据,观察者收到通知后主动从主题拉取最新业务数据。
优点:按需拉取、灵活、适配复杂数据场景,避免无效数据推送。
缺点:观察者依赖主题查询接口,轻微增加耦合,多一次查询开销。
4.1 拉模型业务场景
沿用订单创建场景,主题仅推送「订单状态变更通知」,不传递订单数据;各类观察者收到通知后,主动向订单主题拉取订单详情,完成对应业务处理。
4.2 拉模型核心代码实现
// 抽象观察者(拉模型:无参数更新方法) public interface OrderPullObserver { // 仅接收变更通知,无数据传递 void update(); } // 抽象主题(提供数据查询接口) import java.util.ArrayList; import java.util.List; public abstract class OrderPullSubject { // 维护订阅观察者 protected List<OrderPullObserver> observerList = new ArrayList<>(); // 缓存最新订单号(状态数据) protected String latestOrderNo; // 订阅、取消订阅 public void attach(OrderPullObserver observer){ observerList.add(observer); } public void detach(OrderPullObserver observer){ observerList.remove(observer); } // 通知抽象方法 public abstract void notifyObserver(); // 核心:提供数据拉取接口(供观察者主动查询) public String getLatestOrderNo(){ return this.latestOrderNo; } } // 具体主题 public class OrderPullServiceImpl extends OrderPullSubject { // 创建订单,更新状态并触发通知 public void createOrder(String orderNo){ System.out.println("【拉模型核心业务】订单创建成功:" + orderNo); // 更新主题最新状态 this.latestOrderNo = orderNo; // 仅推送通知,不传递数据 notifyObserver(); } @Override public void notifyObserver() { for (OrderPullObserver observer : observerList) { observer.update(); } } } // 具体观察者:短信通知(主动拉取数据) public class PullSmsObserver implements OrderPullObserver { // 持有主题引用,用于拉取数据 private OrderPullSubject subject; public PullSmsObserver(OrderPullSubject subject){ this.subject = subject; } @Override public void update() { // 主动拉取主题最新数据 String orderNo = subject.getLatestOrderNo(); System.out.println("【拉模型-短信通知】订单"+orderNo+"发送下单成功短信"); } } // 具体观察者:积分发放(主动拉取数据) public class PullScoreObserver implements OrderPullObserver { private OrderPullSubject subject; public PullScoreObserver(OrderPullSubject subject){ this.subject = subject; } @Override public void update() { String orderNo = subject.getLatestOrderNo(); System.out.println("【拉模型-积分发放】订单"+orderNo+"发放消费积分"); } }4.3 拉模型客户端测试
public class PullObserverClient { public static void main(String[] args) { // 1. 创建主题 OrderPullServiceImpl orderService = new OrderPullServiceImpl(); // 2. 注册观察者,注入主题用于拉取数据 orderService.attach(new PullSmsObserver(orderService)); orderService.attach(new PullScoreObserver(orderService)); // 3. 触发业务,推送空通知 orderService.createOrder("ORD20260707002"); } }5. 推模型 vs 拉模型核心区别(面试必背)
5.1 推模型
特点:主题主动推送完整数据给观察者。
优点:观察者无需依赖主题、无需主动查询,效率高。
缺点:数据冗余、灵活性差,推送过多无用字段。
5.2 拉模型
特点:主题只推送变更通知,观察者主动拉取详细数据。
优点:按需拉取、灵活、适配复杂数据场景。
缺点:观察者依赖主题接口,增加耦合、多一次查询开销。
6. 观察者模式优缺点
6.1 优点
1. 彻底解耦发布与订阅:主流程无需关心后置业务,符合单一职责。
2. 完美符合开闭原则:新增联动业务只需新增观察者,无需改主流程。
3. 动态订阅、灵活拓展:运行时可随意注册、注销观察者。
4. 事件统一分发:统一后置处理入口,便于统一监控、异步、降级。
6.2 缺点(针对于上述案例)
1. 默认同步阻塞、主流程变慢:所有观察者串行执行,任意一个卡顿拖垮主流程。
2. 无事务、无重试、易丢失事件:内存级事件,重启丢失、异常中断无补偿。
3. 多级联动排查困难:观察者嵌套订阅会形成隐式链路,排查链路复杂。
4. 内存泄漏风险:观察者未及时注销,集合常驻内存。
7. 企业级成熟实现:SpringBoot异步事件监听(生产落地版)
7.1 业务场景
订单创建成功后,自动触发:短信通知、积分发放、日志记录多个后置业务。
前面的纯JDK手动实现属于教学版同步阻塞写法,存在严重问题:串行执行、主流程阻塞、单个观察者异常雪崩、无法异步、无法统一管控。
Springboot架构下的生产环境,大多使用Spring Event事件机制:底层就是观察者模式,自动管理订阅、支持异步、异常隔离、事务监听、完全解耦,是企业标准落地方案。
7.2 企业级落地优势(对比原生手写)
a.彻底异步非阻塞:支持全局/指定监听异步,不卡主业务RT;
b.异常隔离:单个监听失败不影响其他观察者;
c.无需手动维护观察者集合:Spring IOC自动注册、自动销毁、无内存泄漏;
d.支持事务绑定:可实现「事务成功后再发布事件」,解决数据不一致;
e.原生支持有序、同步/异步混用,工程极度成熟。
7.3 业务场景
订单创建成功后,异步并行执行:短信通知、积分发放、操作日志记录,不阻塞订单创建主流程,单个业务失败互不影响。
6.4 第一步:定义订单事件(事件主体-承载数据)
import org.springframework.context.ApplicationEvent; /** * 订单创建事件(消息载体) * 对应观察者模式:主题状态数据 */ public class OrderCreateEvent extends ApplicationEvent { // 业务数据 private String orderNo; public OrderCreateEvent(Object source, String orderNo) { super(source); this.orderNo = orderNo; } public String getOrderNo() { return orderNo; } }7.5 第二步:定义多个观察者(事件监听器)
所有监听器自动订阅事件,完全符合一对多观察者模型
import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; /** * 短信通知观察者 */ @Component public class SmsEventListener { // 异步注解 @Async // 监听器注解,并指明接收的消息体类 @EventListener(OrderCreateEvent.class) public void handleSms(OrderCreateEvent event) { try { String orderNo = event.getOrderNo(); // 模拟远程调用发短信 System.out.println("【异步短信通知】订单:" + orderNo + " 发送下单短信,线程:" + Thread.currentThread().getName()); } catch (Exception e) { // 生产可扩展:失败入队、重试、告警 System.err.println("短信发送失败!"); } } } /** * 积分发放观察者 */ @Component public class ScoreEventListener { // 异步注解 @Async // 监听器注解,并指明接收的消息体类 @EventListener(OrderCreateEvent.class) public void handleScore(OrderCreateEvent event) { try { String orderNo = event.getOrderNo(); System.out.println("【异步积分发放】订单:" + orderNo + " 发放积分,线程:" + Thread.currentThread().getName()); } catch (Exception e) { System.err.println("积分发放失败!"); } } } /** * 日志记录观察者(可同步执行,保证一定落地) */ @Component public class LogEventListener { // 监听器注解,并指明接收的消息体类 @EventListener(OrderCreateEvent.class) public void handleLog(OrderCreateEvent event) { String orderNo = event.getOrderNo(); System.out.println("【同步日志记录】订单:" + orderNo + " 记录操作日志"); } }7.6 第三步:事件发布器(主题Subject)
发布者完全不依赖任何观察者,彻底解耦、开闭原则满分
import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class OrderService { // 事件分发器(主题通知器) @Resource private ApplicationContext applicationContext; /** * 核心下单业务 */ @Transactional(rollbackFor = Exception.class) public void createOrder(String orderNo) { // 1. 核心主业务(DB落库、事务) System.out.println("【主业务】订单落库成功:" + orderNo + ",主线程:" + Thread.currentThread().getName()); // 2. 发布事件(自动通知所有监听该类的观察者,无需手动遍历) applicationContext.publishEvent(new OrderCreateEvent(this, orderNo)); // 3. 主流程直接结束,不阻塞! System.out.println("【主流程结束】响应客户端,后置业务异步执行"); } }7.7 第四步:开启异步(启动类注解)
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync // 开启Spring异步线程池支持 public class ObserverApplication { public static void main(String[] args) { SpringApplication.run(ObserverApplication.class, args); } }7.8 执行结果 & 核心特性证明
a.主线程瞬间结束,接口响应极快;
b. 短信、积分在独立子线程并行执行,互不阻塞;
c. 日志同步落地保证可靠,业务后置异步解耦;
d. 任意一个监听异常,不会崩掉其他监听和主流程。
8、企业级进阶:事务绑定事件(解决生产最大BUG)
普通@EventListener会出现:事务未提交,监听先执行,查不到数据的严重生产问题。
企业级终极方案:事务提交后再发布事件
import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.event.TransactionPhase; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @Component public class OrderFinalEventListener { /** * 仅在当前事务【成功提交后】异步执行 * 生产最强、最稳、无数据不一致 */ @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void afterOrderCommit(OrderCreateEvent event) { System.out.println("【事务提交后异步执行】最终一致性后置业务:" + event.getOrderNo()); } }对于@TransactionalEventListener注解,phase翻译过来就是阶段、阶段时机,专门控制事件在事务的哪个阶段执行监听逻辑,如下是各种策略:
// 事务提交后执行(最常用) @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) // 事务回滚后执行(补偿日志、告警) @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) // 事务完成(无论成功/失败) @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)9. 企业版 VS 手写基础版核心对比
基础手写版(教学废弃)- 全部同步串行、主接口卡顿 - 一个报错全部崩、无隔离 - 手动维护List,容易内存泄漏 - 无法结合事务、生产极易出BUG - 新增观察者需要改代码、不优雅
Spring Event 企业版(生产首选)- 异步并行、零阻塞、接口RT极低 - 每个监听器独立异常隔离 - Spring自动注册销毁、无内存泄漏 - 支持事务前/后/回滚精准触发 - 新增后置业务只加监听器,不改主代码,完美开闭。
10. 工程常见问题与解决方案
10.1 同步阻塞导致主接口RT高
解决方案:引入线程池异步执行观察者逻辑,主流程不阻塞。
10.2 事件丢失、异常雪崩
解决方案:每个观察者独立try-catch隔离,单个失败不影响其他订阅者。
10.3 内存泄漏
解决方案:组件销毁时主动detach注销观察者,避免集合常驻引用。
10.4 执行顺序不可控
解决方案:拓展有序观察者、自定义权重排序,控制执行优先级。
11. 重点拓展:观察者模式 与 消息队列MQ深度对比
面试超级高频:观察者模式能不能替代MQ?MQ和观察者的本质区别是什么?
11.1 核心关系:MQ本质就是「分布式观察者模式」
本地观察者模式:内存级、进程内、同步/异步事件分发。
消息队列MQ:跨进程、跨服务、分布式、可持久化的高级观察者模式。
11.2 详细区别(工程落地必背)
a. 作用范围不同
观察者:单机进程内,只解耦本地代码逻辑。
MQ消息队列:分布式跨服务,解耦微服务之间调用。
b. 数据可靠性完全不同
观察者:无持久化,重启丢失、异常丢失、无重试。
MQ:持久化消息、ACK确认、重试机制、死信队列,保证消息可靠投递。
c. 性能削峰能力不同
观察者:无削峰,瞬时流量全部压给订阅者。
MQ:流量削峰、异步缓冲、堆积能力,抗高并发。
d. 解耦层级不同
观察者:代码层级解耦。
MQ:服务架构层级解耦。
e. 扩展性不同
观察者:单机订阅数量有限。
MQ:支持多消费者集群、广播、分片、重试、延迟队列。
11.3 落地选型规范(企业级标准)
使用本地观察者模式:单机业务、无需持久化、事件允许丢失、简单后置联动。
必须升级为MQ消息队列:跨服务联动、需要可靠投递、高并发削峰、需要重试恢复、分布式场景。
12. 高频面试:易混模式区分
12.1 观察者模式 vs 策略模式
观察者:一对多事件通知,发布触发多个后置动作,侧重事件联动。
策略模式:一对一算法替换,动态切换单一执行逻辑,侧重算法替换。
12.2 观察者模式 vs 中介者模式
观察者:一对多,主题统一通知,无复杂交互。
中介者:多对多复杂交互,所有对象通过中介通信,彻底消除互相依赖。
13. 主流框架源码落地(面试高频)
13.1 JDK 原生Observer
JDK 内置 Observer接口、Observable 主题类,是标准的观察者模式原生实现(已过时,但面试常问)。
13.2 Spring事件机制(经典落地)
Spring ApplicationEvent、ApplicationListener、ApplicationContext完全是观察者模式:
发布事件 → 容器统一分发 → 所有监听器自动消费,是工程中最常用的本地观察者实现。
13.3 注册器、监控、配置中心
Nacos、Eureka服务上下线通知、配置变更推送,底层全部基于观察者事件驱动。
14. 核心总结
观察者模式的核心本质:事件驱动、一对多联动、发布订阅解耦,是所有异步事件机制、消息机制的底层原型。
工程落地核心原则:
1. 单机简单联动、允许轻微丢事件:优先观察者模式+线程池异步,轻量高效;
2. 跨服务、高可靠、高并发、分布式场景:必须升级 MQ 消息队列;
3. 观察者是「代码级解耦」,MQ是「架构级解耦」,二者是演进关系而非替代关系;
4. 严格做好异步、异常隔离、主动注销,规避阻塞、内存泄漏、事件雪崩问题。
