使用观察者模式有哪些常见误区?
简化版
观察者模式常见误区包括:把核心强一致流程拆成事件、以为发布事件一定异步、观察者职责过重、事件对象设计过胖或过瘦、不处理异常和顺序、忘记取消订阅导致内存泄漏,以及把观察者模式和消息队列能力混为一谈。
详细版
使用观察者模式时要避免这些问题:
- 把必须立即成功的核心链路拆成隐式观察者,导致一致性变差;
- 误以为事件通知默认异步,忽略同步监听器对主流程的影响;
- 观察者里写大量重业务逻辑,拖慢通知链;
- 事件对象字段过多,暴露不必要信息;
- 事件对象只有一个 ID,导致所有观察者重复查询;
- 没有设计异常隔离,一个观察者失败影响所有观察者;
- 忘记取消订阅,造成内存泄漏;
- 需要可靠投递时仍只用本地观察者,缺少重试和补偿。
面试中可以强调:观察者模式适合解耦扩展点,但工程落地必须明确同步异步、事务边界、异常处理、幂等和可观测性。
完整版教学
一、误区一:核心流程也随意事件化
不是所有后续动作都适合放到观察者里。
如果某个步骤是主流程成功的必要条件,就不应该随意拆成不透明事件。
例如支付成功前必须完成扣款、更新订单状态、记录支付流水,这些属于核心一致性链路。它们应该在主流程或明确的事务编排中完成。
而发送短信、写运营日志、同步搜索索引,更适合作为观察者或异步事件。
二、误区二:以为发布事件一定异步
很多框架的事件机制默认是同步的。比如 Spring ApplicationEvent 默认通常会在当前线程内调用监听器。
如果监听器里调用外部接口很慢,发布事件的主流程也会变慢。
所以设计时必须明确:
- 当前事件是同步还是异步;
- 监听器异常会不会影响发布方;
- 是否需要线程池隔离;
- 是否需要事务提交后再触发。
不能看到 publishEvent 就默认它是异步消息。
三、误区三:观察者职责不单一
观察者应该处理一个清晰的响应动作。
坏例子是一个 OrderPaidListener 里同时做短信、积分、营销、日志、缓存、统计。这样只是把上帝类从订单服务搬到了监听器。
更合理的拆法是:
SmsOnOrderPaidListenerPointOnOrderPaidListenerLogOnOrderPaidListener
这样每个监听器都能独立测试、独立排查。
四、误区四:事件对象设计失衡
事件对象太胖,会导致事件难维护,也可能泄露无关数据。
事件对象太瘦,又会导致观察者频繁回查数据库或远程服务。
比较稳的原则是:事件对象携带“证明该事件已经发生的核心事实”,例如订单 ID、用户 ID、金额、发生时间;复杂详情由观察者按需查询。
这和推模型、拉模型的取舍是一致的。
五、误区五:没有异常、顺序和幂等设计
观察者模式如果没有异常隔离,一个监听器失败可能影响其他监听器。
如果多个监听器存在顺序依赖,又没有显式顺序控制,就可能出现偶发 bug。
如果是异步通知,还要考虑事件重复处理。比如发券监听器收到重复事件,必须保证不会重复发券。
工程上至少要考虑:
- 单个观察者失败如何处理;
- 是否需要指定顺序;
- 是否需要幂等键;
- 是否需要重试或补偿;
- 是否需要监控每个观察者耗时和失败率。
六、误区六:把本地观察者当成消息队列
本地观察者模式通常不具备消息队列的可靠投递能力。
如果进程崩溃,内存里的事件可能直接丢失;如果监听器失败,也不一定有重试、死信、堆积和消费确认。
所以跨服务、关键业务事件、需要可靠投递时,应该使用消息队列、本地消息表或事务消息等机制,而不是只靠本地观察者。
七、从业务事实到投递语义逐层检查
支付成功事件即使只发布 1 次,异步消费者也可能因重试收到 2 次;积分监听器需用 eventId 去重,确保最终只入账 1 次。
business fact -> event contract -> delivery(at least once?) -> idempotent observer -> monitoring
这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。
八、边界、代价与验证
| 检查维度 | 应确认的内容 |
|---|---|
| 正确性 | 事件化之前先写清楚:何时发布、是否持久化、失败怎么办、能否重复、由谁观察。 |
| 适用边界 | 不要让监听器悄悄承担决定事务成败的核心步骤,也不要依赖未声明的执行顺序。 |
| 测试证据 | 覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序 |
| 运行成本 | 记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量 |
模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。
易错点:事件化之前先写清楚:何时发布、是否持久化、失败怎么办、能否重复、由谁观察。
九、常见误区与追问
- 误区:发布方只调用一次就不会重复消费。 网络重试、进程恢复和消费者失败都可能造成重复投递,次数语义要显式设计。
- 误区:发布事件天然等于异步和最终一致性。 观察者只定义一对多通知关系;同步还是异步、是否跨进程以及一致性语义都由具体实现决定。
- 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
- 追问:怎样识别过度事件化? 如果读主流程代码已无法判断关键结果是否完成,且监听器之间存在强顺序依赖,就应收回显式编排。
- 追问:观察者失败后由谁重试? 模式本身没有答案;同步场景要定义异常传播,异步场景要由执行器、事件总线或消息系统提供重试与死信策略。
- 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。
十、加强记忆
观察者模式的误区可以记成“别乱事件化、别误判异步、别让监听器变胖、别忽略异常顺序幂等、别把本地事件当消息队列”。它适合应用内解耦和扩展点,不等于天然可靠的分布式事件系统。