Skip to content

观察者模式

观察者模式用于对象之间的一对多通知。当一个对象状态变化时,它不直接调用所有后续逻辑,而是发布事件,让多个观察者各自处理。

为什么需要观察者模式

用户注册成功后,可能要做很多事:

  1. 发送欢迎短信。
  2. 发放新人优惠券。
  3. 初始化用户积分。
  4. 记录注册日志。
  5. 发送运营通知。

如果都写在注册方法里:

java
public void register(User user) {
    userMapper.insert(user);
    smsService.sendWelcome(user);
    couponService.sendNewUserCoupon(user);
    pointService.initPoint(user);
    logService.record(user);
}

问题:

  1. 注册主流程越来越臃肿。
  2. 新增后续动作要改注册代码。
  3. 某个非核心动作失败可能影响注册。
  4. 主流程知道太多下游细节。

观察者模式把后续动作拆出去:

mermaid
flowchart TD
    A["用户注册成功"] --> B["发布 UserRegisteredEvent"]
    B --> C["短信监听器"]
    B --> D["优惠券监听器"]
    B --> E["积分监听器"]
    B --> F["日志监听器"]

基本结构

mermaid
flowchart TD
    A["Subject 主题"] --> B["Observer A"]
    A --> C["Observer B"]
    A --> D["Observer C"]
    A --> E["状态变化时 notify"]
角色作用
Subject保存观察者,状态变化时通知
Observer定义收到通知后的处理接口
ConcreteObserver具体观察者,执行自己的逻辑

Java 简单 Demo

事件:

java
public class UserRegisteredEvent {
    private final Long userId;

    public UserRegisteredEvent(Long userId) {
        this.userId = userId;
    }

    public Long getUserId() {
        return userId;
    }
}

监听器接口:

java
public interface EventListener {
    void onEvent(UserRegisteredEvent event);
}

具体监听器:

java
public class SmsListener implements EventListener {
    @Override
    public void onEvent(UserRegisteredEvent event) {
        System.out.println("send sms to user: " + event.getUserId());
    }
}

发布器:

java
public class EventPublisher {
    private final List<EventListener> listeners = new ArrayList<>();

    public void addListener(EventListener listener) {
        listeners.add(listener);
    }

    public void publish(UserRegisteredEvent event) {
        for (EventListener listener : listeners) {
            listener.onEvent(event);
        }
    }
}

Spring 事件机制

Spring 中可以使用 ApplicationEventPublisher 发布进程内事件。

java
@Service
public class UserService {
    private final ApplicationEventPublisher publisher;

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

    public void register(Long userId) {
        // 保存用户
        publisher.publishEvent(new UserRegisteredEvent(userId));
    }
}

监听:

java
@Component
public class CouponListener {
    @EventListener
    public void handle(UserRegisteredEvent event) {
        System.out.println("send coupon to user: " + event.getUserId());
    }
}

和消息队列的区别

对比项观察者模式消息队列
范围进程内为主跨进程、跨服务
可靠性依赖应用进程Broker 可持久化和重试
性能本地调用快有网络和 Broker 成本
失败处理要自己定义通常有 ACK、重试、死信
场景本地扩展点分布式异步通信

如果用户注册后要通知另一个微服务,应该使用 消息队列,而不是进程内观察者。

风险和边界

风险后果建议
监听器太多一次事件触发大量逻辑,难排查只放明确相关的扩展动作
监听器失败是否影响主流程不清楚明确定义同步/异步和异常策略
监听器互相依赖顺序耦合,失去解耦价值观察者之间不要互相依赖
用本地事件替代 MQ服务重启或跨服务消息丢失可靠事件用 MQ

小结

观察者模式适合“主流程完成后,需要通知多个独立后续动作”的场景。它的价值是降低主流程和扩展逻辑的耦合,但它不是可靠消息系统。是否同步执行、失败是否影响主流程、是否需要重试,都要在设计时说清楚。