你写的事件监听,其实就是观察者模式

4 阅读1分钟

你写的事件监听,其实就是观察者模式

上周我在改一段代码,逻辑大概是这样的:用户下单成功之后,要发短信、更新积分、通知仓库备货。三件事全部堆在 createOrder() 方法里,一个方法一百多行。

改的时候我就在想,这三件事跟下单本身有什么关系?下单的逻辑不应该知道"下完之后要做什么",它只需要知道"下单成功了"。

这就是观察者模式想解决的问题。


最简单的实现

观察者模式只有两个概念:一个是被观察的对象(Subject),一个是观察它的人(Observer)。Subject 状态变化时,通知所有注册的 Observer。

public interface Observer {
    void update(String event, Object data);
}

public class OrderEventPublisher {
    private List<Observer> observers = new ArrayList<>();

    public void subscribe(Observer observer) {
        observers.add(observer);
    }

    public void publish(String event, Object data) {
        for (Observer observer : observers) {
            observer.update(event, data);
        }
    }
}

然后每个后续操作独立实现 Observer:

public class SmsNotifier implements Observer {
    public void update(String event, Object data) {
        if ("ORDER_CREATED".equals(event)) {
            // 发短信
        }
    }
}

public class InventoryNotifier implements Observer {
    public void update(String event, Object data) {
        if ("ORDER_CREATED".equals(event)) {
            // 通知仓库
        }
    }
}

下单代码变成这样:

public void createOrder(OrderDTO dto) {
    // 下单逻辑
    Order order = orderRepo.save(dto);
    
    // 发事件,不关心谁在听
    publisher.publish("ORDER_CREATED", order);
}

Spring 里你已经在用了

Spring 的 ApplicationEvent 体系就是观察者模式的实现,用起来比自己写的要干净:

// 定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
    
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
    
    public Order getOrder() { return order; }
}

// 发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void createOrder(OrderDTO dto) {
        Order order = orderRepo.save(dto);
        eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
    }
}

// 监听事件
@Component
public class SmsNotifier {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        Order order = event.getOrder();
        // 发短信
    }
}

要异步执行就加 @Async,整个下单流程和后续操作完全解耦。加一个新的后续操作,不需要碰 createOrder() 一行代码。


一个容易踩的坑

观察者默认是同步的。Spring 的 @EventListener 如果不加 @Async,监听方法会在同一个事务里执行。

意味着如果发短信这步抛了异常,下单事务也会回滚。这通常不是你想要的行为。

处理方式:

// 方式一:@Async 异步执行
@Async
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
    smsService.send(event.getOrder());
}

// 方式二:事务提交后执行
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent event) {
    smsService.send(event.getOrder());
}

@TransactionalEventListener 是我更常用的,语义清晰:下单成功、事务提交完才发短信,不会出现事务回滚了短信却发出去的情况。


和责任链的区别

观察者是广播,一个事件多个监听者同时处理,互不知道对方存在。

责任链是流水线,一个请求按顺序经过多个处理者,每个处理者可以拦截或放行。

下单之后发通知,用观察者。请求进来做鉴权、日志、限流,用责任链。混用就会出问题。


什么时候不该用

事件链长了之后调试很痛苦。A 事件触发 B,B 又触发 C,出了问题你需要顺着事件链一路找。

如果业务逻辑本身是强依赖的(比如扣库存失败就不能下单),就不要用事件解耦,直接同步调用更清楚。

观察者适合那种"做完这件事,顺便通知一下相关方"的场景,不适合"做完这件事,必须等另一件事确认成功才算完"的场景。


23种设计模式我都画成了漫画闯关游戏,放在微信小程序「爪爪代码冒险记」里。主角是一只卡皮巴拉,每个模式一个关卡,比背书有意思。微信搜一下就能找到。