Observer 与 Pub/Sub 有何不同

1. 总览

Observer 和 Pub/Sub 都用来解决“一个事件发生,多处需要知道”的问题。很多开发者混淆这两个概念,因为它们的思想相似,但在实现方式和耦合程度上有所不同。本文帮助你清楚地区分这两个概念,并提供了各种方式的 Java 代码示例,教你何时选择纯 Observer 模式,何时选择通过 message broker 的 Pub/Sub 架构。

2. Observer Pattern 是什么

Observer 是 GoF 经典行为型模式(behavioral pattern)之一。其思想是:一个 Subject(或 Publisher)维护一个 Observer 列表,当状态改变时,Subject 直接调用每个已注册 Observer 的方法。

public interface OrderObserver {
    void onOrderPaid(Order order);
}

public class Order {
    private final List<OrderObserver> observers = new ArrayList<>();
    private String status = "NEW";

    public void addObserver(OrderObserver observer) {
        observers.add(observer);
    }

    public void markAsPaid() {
        this.status = "PAID";
        for (OrderObserver observer : observers) {
            observer.onOrderPaid(this);
        }
    }
}

解释:

  • observers 是直接持有感兴趣对象引用的列表。
  • markAsPaid() 依次在每个 observer 上调用 onOrderPaid(),同一线程中,同步执行。
  • Order 知道谁在监听它——这是与 Pub/Sub 区分的关键点。
public class EmailNotifier implements OrderObserver {
    @Override
    public void onOrderPaid(Order order) {
        System.out.println("为已支付订单发送确认邮件");
    }
}
Order order = new Order();
order.addObserver(new EmailNotifier());
order.markAsPaid();
为已支付订单发送确认邮件

2.1. Observer 的核心特点

  • 直接耦合:Subject 持有对 Observer 的引用,需要相互导入接口。
  • 同步:notify 调用会阻塞,直到所有 observer 处理完毕。
  • 进程内:通常在同一个 JVM 内运行,不涉及网络。
  • Subject 知道 Observer 存在:如果忘记调用 removeObserver(),容易产生内存泄漏,因为 Subject 一直持有已废弃 observer 的引用。

注意:经典 Observer pattern 本身没有安全的 unsubscribe 机制。如果你在长生命周期的应用中使用(比如 UI 组件、注册后销毁的监听器),记得在对象销毁时调用 removeObserver(),避免内存泄漏。

3. Pub/Sub 是什么

Pub/Sub(发布/订阅)通过一个中间层——称为 broker、event bus 或 topic——完全解耦 Publisher 和 Subscriber。Publisher 不知道谁在订阅,只是把消息“发射”到某个 topic/channel。

public record OrderPaidEvent(String orderId, double amount) {}
public class OrderService {
    private final EventBus eventBus;

    public OrderService(EventBus eventBus) {
        this.eventBus = eventBus;
    }

    public void payOrder(String orderId, double amount) {
        // 处理支付...
        eventBus.publish("order.paid", new OrderPaidEvent(orderId, amount));
    }
}

解释:

  • OrderService 不知道 EmailNotifier 或 InventoryService 是否存在。
  • 它只向 "order.paid" topic 发布,剩下的由 broker 处理。
  • Subscriber 向 broker 注册,而不是直接向 OrderService 注册。

3.1. 进程内 Pub/Sub 与 Spring Events

Spring Boot 内置了一个简单的 event bus,使用 ApplicationEventPublisher。这是应用层的 Pub/Sub,还不需要 Kafka/RabbitMQ 这样的外部 broker。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
public class OrderPaidEvent extends ApplicationEvent {
    private final String orderId;

    public OrderPaidEvent(String orderId) {
        super(orderId);
        this.orderId = orderId;
    }

    public String getOrderId() {
        return orderId;
    }
}
@Service
public class OrderService {
    private final ApplicationEventPublisher publisher;

    public OrderService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void payOrder(String orderId) {
        publisher.publishEvent(new OrderPaidEvent(orderId));
    }
}
@Component
public class EmailEventListener {

    @EventListener
    public void handle(OrderPaidEvent event) {
        System.out.println("为订单 " + event.getOrderId() + " 发送邮件");
    }
}

解释:

  • OrderService 通过 ApplicationEventPublisher——由 Spring 管理的 bean、充当内部 broker 的角色——发布 event。
  • EmailEventListener 不被 OrderService 知晓;Spring 自动扫描带 @EventListener 的方法,并根据事件类型路由到合适的 handler。
  • 默认情况下,@EventListener 与 publisher 同步在同一线程中运行——这容易造成混淆,很多人认为 Pub/Sub 总是异步的。

要实现真正的异步,加上 @Async:

@Component
public class EmailEventListener {

    @Async
    @EventListener
    public void handle(OrderPaidEvent event) {
        System.out.println("为订单 " + event.getOrderId() + " 发送邮件");
    }
}
@Configuration
@EnableAsync
public class AsyncConfig {
}
  • @EnableAsync 激活代理,使 Spring 在独立的 thread pool 上运行 @Async 方法。
  • 现在 payOrder() 立即返回,不等待 EmailEventListener 处理完成。

3.2. 通过 Message Broker 的 Pub/Sub(Kafka)

在更大规模的系统中,“broker” 不再是 JVM 中的一个 bean,而是独立的系统(Kafka、RabbitMQ、SQS 等)。Publisher 和 subscriber 可能位于不同的服务、不同的进程、不同的机器上。

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
@Service
public class OrderService {
    private final KafkaTemplate<String, OrderPaidEvent> kafkaTemplate;

    public OrderService(KafkaTemplate<String, OrderPaidEvent> kafkaTemplate) {
        this.kafkaTemplate = kafkaTemplate;
    }

    public void payOrder(String orderId, double amount) {
        kafkaTemplate.send("order-paid-topic", new OrderPaidEvent(orderId, amount));
    }
}
@Component
public class EmailConsumer {

    @KafkaListener(topics = "order-paid-topic", groupId = "email-service")
    public void consume(OrderPaidEvent event) {
        System.out.println("邮件服务处理订单 " + event.orderId());
    }
}
  • OrderService 和 EmailConsumer 可能位于两个完全独立的微服务中,独立部署。
  • Kafka 在 topic 中保存消息,consumer 按自己的速率消费——这是真正的 Pub/Sub,具有缓冲、重放、多个独立 consumer group 等能力。

4. 直接对比

对比项 Observer Pub/Sub
耦合程度 Subject 直接了解 Observer(通过接口) Publisher 不知道 Subscriber 是否存在
中间层 无,直接调用方法 有 broker/event bus 在中间
同步/异步 默认同步,同一线程 可同步(Spring Event)或异步(Kafka、RabbitMQ)
范围 单个进程/模块内 进程内(event bus)或跨多个服务/进程
消息持久性 无,Subject 失效就丢失 外部 broker(Kafka)可保存,允许重放
Publisher-Subscriber 数量 通常 1 个 Subject - 多个 Observer 多个 Publisher - 多个 Subscriber,通过多个 topic

最重要的区别:Observer 是两个对象间的直接关系,Pub/Sub 是通过中间层的间接关系。本质上,Pub/Sub 可看作 Observer 模式的“松耦合”版本,通过引入 broker 来消除直接依赖。

5. 何时用 Observer,何时用 Pub/Sub

使用 Observer 当:

  • 逻辑在同一模块/类中,observer 数量固定且变化不大。
  • 你需要即时同步响应(比如在保存前验证数据)。
  • 不需要过度解耦——比如 UI 组件监听 model 的变化。
class ValidationObserver implements OrderObserver {
    @Override
    public void onOrderPaid(Order order) {
        if (order.getStatus() == null) {
            throw new IllegalStateException("订单状态无效");
        }
    }
}

使用 Pub/Sub 当:

  • 多个独立服务需要了解某个事件,但不应相互依赖。
  • 需要灵活地添加/删除 subscriber,不改动 publisher 代码。
  • 需要消息持久性(如果 subscriber 离线,消息会等待其重新上线)。
  • 系统是微服务架构,通过网络通信。

提示:不要仅为了替换同一个 JVM 中的 List<Observer> 就使用 Kafka/RabbitMQ——broker 的运维成本不值得这种小问题。反过来,也别试图用纯 Observer 模式硬撑跨服务的业务逻辑,因为你得自己实现 broker 已提供的网络、重试、序列化等全套机制。

6. Observer 行为测试

class OrderObserverTest {

    @Test
    void shouldNotifyAllObserversWhenOrderIsPaid() {
        Order order = new Order();
        List<String> notified = new ArrayList<>();

        order.addObserver(o -> notified.add("email"));
        order.addObserver(o -> notified.add("inventory"));

        order.markAsPaid();

        assertThat(notified).containsExactly("email", "inventory");
    }
}

解释:

  • 用 lambda 代替 OrderObserver 的实现类,因为接口只有一个方法——写测试时更简洁。
  • assertThat(...).containsExactly(...) 验证 observer 的调用顺序与注册顺序相同——这是同步 Observer 的特点,不同于 Pub/Sub 通过 broker(Kafka 可能多个 consumer instance 并行处理)。

7. 常见错误

  • 误认为 Spring @EventListener 是异步的:默认是同步的,listener 中的异常会向上抛给 publisher,除非你捕捉它。
  • Observer 持有引用但忘记移除:导致内存泄漏,尤其在应用中对象生命周期短(request-scope)但注册到 singleton 时。
  • 滥用异步 Pub/Sub 处理需要事务一致性的逻辑:如果 publisher 和 subscriber 必须同时成功或失败(原子性),异步 Pub/Sub 不适合——应该考虑 Saga 模式或 transactional outbox。
  • topic 命名不清晰:在大系统中,用模糊的名字如 "event1" 会让人不知道谁订阅谁,失去了解耦的好处。

8. 总结

Observer 和 Pub/Sub 都想解决“通知多个感兴趣方”的问题,但在耦合程度和作用范围上不同。Observer 适合直接关系、同步、进程内的通信;Pub/Sub 适合完全解耦 publisher 和 subscriber,尤其在需要消息持久性和多个独立 consumer 的微服务系统中。理解这些差异能帮你选对工具:不是每时每刻都需要 Kafka,也不是 List<Observer> 总能胜任你的需求。

design-patternobserverpub-subjavaspring-boot