← 返回题目列表

观察者模式中的推模型和拉模型有什么区别?

高频 中等 第 8 / 27 题 更新于 2026/07/28
观察者模式推模型拉模型事件设计

简化版

推模型是主题通知观察者时直接把事件数据传过去,观察者拿到数据就能处理;拉模型是主题只通知“发生变化”,观察者再主动向主题查询需要的数据。推模型简单直接,拉模型更灵活但耦合和查询成本更高。

详细版

观察者模式里,主题通知观察者时有两种常见数据传递方式。

推模型:

  • 主题把事件数据主动传给观察者;
  • 观察者不需要再查询主题;
  • 适合事件数据明确、字段不太重的场景。

拉模型:

  • 主题只告诉观察者“状态变了”;
  • 观察者根据需要再去主题或其他服务查询数据;
  • 适合数据量大、观察者需求差异很大的场景。

工程里更常用的是推模型,尤其是业务事件,例如订单支付成功事件会携带订单号、用户 ID、支付金额等关键信息。但如果事件数据非常大,或者观察者需要的数据差异很大,可以只推 ID,让观察者按需拉取。

完整版教学

一、推模型是什么

推模型的意思是:事件发布时,把观察者处理所需的数据一并推过去。

例如订单支付成功:

public class OrderPaidEvent {
    private Long orderId;
    private Long userId;
    private BigDecimal amount;
}

观察者收到事件后直接处理:

public void onOrderPaid(OrderPaidEvent event) {
    sendSms(event.getUserId(), event.getAmount());
}

推模型的优点是简单、直观、减少二次查询。缺点是事件对象可能越来越大,因为不同观察者想要的字段不一样。

二、拉模型是什么

拉模型的意思是:主题只通知事件发生,观察者自己去拉取需要的数据。

例如事件里只放订单 ID:

public class OrderPaidEvent {
    private Long orderId;
}

观察者收到事件后再查询订单服务:

Order order = orderService.getById(event.getOrderId());

拉模型的优点是事件轻量,观察者可以按需获取数据。缺点是可能带来额外查询、接口依赖和一致性问题。

三、两种模型的核心取舍

推模型像“把资料一起发给你”,拉模型像“告诉你资料更新了,你自己去查”。

推模型适合:

  • 事件字段比较明确;
  • 大多数观察者都需要相同数据;
  • 希望减少查询;
  • 希望事件可审计、可重放。

拉模型适合:

  • 数据量很大;
  • 不同观察者需要的数据差异明显;
  • 数据变化快,事件只适合放主键;
  • 发布方不想承载太多字段。

四、真实项目经常是混合模型

很多系统不会纯粹推或纯粹拉,而是采用混合方式:事件中推送核心字段,观察者必要时再拉详细数据。

例如:

事件推送:orderId、userId、amount、paidAt
观察者按需拉取:商品明细、收货地址、营销标签

这样既保证常用信息可直接处理,又避免事件对象过度膨胀。

五、事件字段设计要避免两个极端

第一个极端是事件太瘦,只有一个 ID。所有观察者都去查数据库或远程服务,容易造成查询风暴。

第二个极端是事件太胖,把订单、用户、商品、优惠、地址、库存信息全部塞进去。事件变得难维护,也可能暴露不该暴露的数据。

比较稳的设计是:事件携带“证明这个事件发生所需的核心事实”,其他扩展信息按需查询。

六、事件信息量与一致性窗口

推模型携带 8 个字段,每个观察者都收到约 1 KB;拉模型只推 1 个 id,但 5 个观察者可能额外产生 5 次查询。

push: Subject -> full event; pull: Subject -> id -> observers query source

这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。

七、边界、代价与验证

检查维度应确认的内容
正确性选择推或拉,本质是在事件稳定性、负载和观察者自主性之间做权衡。
适用边界推太多会扩大契约和传输成本,推太少会造成 N 次查询及读取时状态已变化;工程上常用 id 加关键快照的混合模型。
测试证据覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序
运行成本记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量

模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。

易错点:选择推或拉,本质是在事件稳定性、负载和观察者自主性之间做权衡。

八、常见误区与追问

  • 误区:拉模型一定能读到事件发生时的状态。 观察者查询时源对象可能已变化;需要历史快照时必须在事件中携带版本或快照。
  • 误区:发布事件天然等于异步和最终一致性。 观察者只定义一对多通知关系;同步还是异步、是否跨进程以及一致性语义都由具体实现决定。
  • 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
  • 追问:事件对象应包含哪些字段? 至少包含身份、发生时间或版本及处理所需稳定事实,避免塞入整个可变聚合对象。
  • 追问:观察者失败后由谁重试? 模式本身没有答案;同步场景要定义异常传播,异步场景要由执行器、事件总线或消息系统提供重试与死信策略。
  • 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。

九、加强记忆

推模型是“主题把数据推给观察者”,拉模型是“主题只通知变化,观察者自己查数据”。工程上常用混合方案:事件里放核心字段和主键,复杂详情按需拉取,避免事件过瘦或过胖。