29、重构与设计模式(二):用观察者模式重构耦合的更新逻辑

上一讲我们聊了策略模式,这次来谈谈观察者模式。说实话,这个模式是我在实际项目中用得最频繁的之一。为什么?因为业务系统里到处都是「当A发生了,B、C、D都要跟着动」的场景。你想想看,如果每次新增一个「跟着动」的模块,都要去改A的代码,那得多痛苦。

我接手过一个老项目,里面有个订单状态变更的方法,足足有三百多行。里面混着发短信、发邮件、更新库存、通知物流、推送消息……每次改一个逻辑,都得小心翼翼,生怕碰坏了别的东西。嗯,这就是典型的「耦合的更新逻辑」。

什么是观察者模式?

观察者模式,说白了就是「发布-订阅」机制。一个对象(被观察者)维护一组依赖它的对象(观察者),当自身状态发生变化时,自动通知所有观察者。

核心角色就三个:

  • Subject(被观察者):持有观察者列表,提供注册、移除、通知的方法
  • Observer(观察者):定义一个更新接口,接收被观察者的通知
  • ConcreteObserver(具体观察者):实现更新接口,执行具体的响应逻辑

核心思想:让被观察者不关心谁在观察它,只负责「通知」这件事。观察者自己决定收到通知后做什么。

观察者模式结构图 Subject(被观察者) - observers: List<Observer> + attach(Observer) + detach(Observer) + notify() Observer(观察者) + update(event) 接口 / 抽象类 通知 ConcreteSubject + getState(): state ConcreteObserver1 + update(event) ConcreteObserver2 + update(event) 被观察者只负责通知,不关心谁在监听

重构前的代码:一团乱麻

先看看重构前的样子。这是一个订单服务,当订单状态变为「已支付」时,需要执行一堆操作:

public class OrderService {
    public void payOrder(String orderId) {
        // 1. 更新订单状态
        Order order = orderRepository.findById(orderId);
        order.setStatus(OrderStatus.PAID);
        orderRepository.save(order);
        
        // 2. 发短信通知用户
        smsService.send(order.getUserId(), "您的订单已支付成功");
        
        // 3. 发邮件通知
        emailService.send(order.getUserId(), "订单支付确认", "您的订单已支付...");
        
        // 4. 更新库存
        inventoryService.deduct(order.getItems());
        
        // 5. 通知物流系统
        logisticsService.createDelivery(orderId);
        
        // 6. 推送消息给运营后台
        pushService.notify("order_paid", order);
        
        // 7. 记录日志
        logService.record("order_paid", orderId);
    }
}

这段代码的问题很明显:

  • 职责不单一:一个方法干了七八件事
  • 扩展困难:每次新增一个「支付后要做的操作」,都得改这个方法
  • 测试麻烦:要 mock 一堆服务,才能测这个核心逻辑
  • 耦合度高:OrderService 依赖了所有具体服务

我曾经见过一个项目,类似的「状态变更后处理」方法里,有人直接写了调用第三方 API 的代码。结果第三方接口挂了,整个订单流程都卡住了。这就是耦合的代价——一个无关模块的问题,能拖垮核心流程。

重构:引入观察者模式

重构的思路很简单:把「支付完成」这个事件抽象出来,让各个模块自己去订阅这个事件。OrderService 只负责发布事件,不关心谁在处理。

先定义事件和观察者接口:

// 事件对象,携带上下文信息
public class OrderPaidEvent {
    private String orderId;
    private String userId;
    private List<OrderItem> items;
    // getter / setter 省略
}

// 观察者接口
public interface OrderObserver {
    void onOrderPaid(OrderPaidEvent event);
}

然后改造 OrderService:

public class OrderService {
    private List<OrderObserver> observers = new ArrayList<>();
    
    public void attach(OrderObserver observer) {
        observers.add(observer);
    }
    
    public void detach(OrderObserver observer) {
        observers.remove(observer);
    }
    
    public void payOrder(String orderId) {
        // 核心逻辑:只做订单状态变更
        Order order = orderRepository.findById(orderId);
        order.setStatus(OrderStatus.PAID);
        orderRepository.save(order);
        
        // 发布事件,通知所有观察者
        OrderPaidEvent event = new OrderPaidEvent();
        event.setOrderId(orderId);
        event.setUserId(order.getUserId());
        event.setItems(order.getItems());
        
        for (OrderObserver observer : observers) {
            observer.onOrderPaid(event);
        }
    }
}

各个观察者独立实现:

public class SmsObserver implements OrderObserver {
    @Override
    public void onOrderPaid(OrderPaidEvent event) {
        smsService.send(event.getUserId(), "您的订单已支付成功");
    }
}

public class EmailObserver implements OrderObserver {
    @Override
    public void onOrderPaid(OrderPaidEvent event) {
        emailService.send(event.getUserId(), "订单支付确认", "您的订单已支付...");
    }
}

public class InventoryObserver implements OrderObserver {
    @Override
    public void onOrderPaid(OrderPaidEvent event) {
        inventoryService.deduct(event.getItems());
    }
}

// 其他观察者类似...

最后在应用启动时注册观察者:

@Configuration
public class OrderObserverConfig {
    @Bean
    public OrderService orderService(
            SmsObserver smsObserver,
            EmailObserver emailObserver,
            InventoryObserver inventoryObserver) {
        OrderService service = new OrderService();
        service.attach(smsObserver);
        service.attach(emailObserver);
        service.attach(inventoryObserver);
        return service;
    }
}

我个人习惯:在 Spring 项目中,可以用 @EventListener 注解配合 ApplicationEventPublisher,框架已经帮我们实现了观察者模式。但理解底层原理很重要,这样即使不用框架,你也能手写出来。

重构后的收益

维度 重构前 重构后
代码行数(核心方法) 30+ 行,混杂多种逻辑 15 行,只做状态变更和事件发布
扩展性 改核心代码,有风险 新增观察者类即可,无需改核心
可测试性 需要 mock 所有依赖 只需验证事件是否发布
职责清晰度 一个方法做所有事 每个观察者只做一件事

避坑指南

我曾经踩过一个坑:观察者里又去调用了其他被观察者的方法,结果形成了循环通知,系统直接栈溢出。后来我加了个「事件去重」机制,每个事件带唯一 ID,观察者处理前先检查是否处理过。

还有几个要点要注意:

  • 观察者的执行顺序:如果某些观察者有依赖关系,可以用优先级排序,或者拆成多个事件
  • 异常隔离:一个观察者抛异常,不应该影响其他观察者。建议用 try-catch 包裹每个观察者的调用
  • 异步处理:如果观察者逻辑耗时(如发邮件),可以考虑异步执行,避免阻塞主流程
  • 不要滥用:如果只有一两个观察者,或者观察者逻辑极其简单,硬套模式反而增加复杂度

总结一下:观察者模式解决的核心问题,就是「一对多」的依赖关系。当被观察者的状态变化时,所有依赖它的对象都能自动得到通知。说白了,就是把「主动调用」变成「被动监听」,让代码的耦合度降下来,扩展性提上去。

我在项目中用这个模式重构过不下十次,每次都能让代码清爽不少。你想想看,如果下次产品经理说「支付成功后还要加个积分赠送」,你只需要新增一个积分观察者,注册进去就行了。核心的 OrderService 一行代码都不用改——这才是我们追求的可扩展性。

公众号:蓝海资料掘金营,微信 deep3321