停止写“牵一发而动全身”的代码!用观察者模式构建可插拔的业务响应

一场因“遗忘”引发的“库存血案”

那是一个双十一的凌晨,订单量像洪水一样涌入。突然,仓储系统的监控发出了刺耳的警报:多个核心商品的库存变为负数!

数据被污染,超卖已成事实,这是一级生产事故。经过紧张的日志追查,问题定位在一个看似无辜的地方:OrderService 的 orderPaidSuccess 方法。一个月前,为了增加短信通知,有人修改了这里的代码,却不小心注释掉了一行关键的库存扣减调用。

image-20250814221749107

剖析那个牵一发而动全身的“上帝方法”

为了处理一个“订单支付成功”的事件,OrderService中有一个长达数百行的“上帝方法”,负责调用所有下游服务。

@Service
public class OrderService {
    @Autowired 
    private SmsService smsService;
    @Autowired 
    private EmailService emailService;
    @Autowired 
    private InventoryService inventoryService;
    @Autowired 
    private AnalyticsService analyticsService;

    public void orderPaidSuccess(Order order) {
        // ... 订单自身状态变更的逻辑 ...
        System.out.println("订单 " + order.getId() + " 支付成功,状态更新完毕。");

        // 灾难的开始:硬编码的通知调用链
        // 需求一:给用户发短信
        smsService.sendPaymentSuccessSms(order.getUserId(), order.getId());

        // 需求二:给用户发邮件
        emailService.sendPaymentSuccessEmail(order.getUserId(), order.getId());

        // 需求三:扣减库存
        inventoryService.deductStock(order.getProductId(), order.getQuantity());

        // 需求四:上报分析系统
        analyticsService.trackPayment(order);

        // 如果未来还有“增加用户积分”、“通知财务系统”... 这个调用链会无限延长
    }
}

问题剖析

  1. 违反开闭原则:现在要增加一个新的“支付成功后给用户增加积分”的功能,就必须深入这个“雷区”修改代码,增加对PointService的调用。每次修改,都有可能像这次事故一样,误删或改错现有逻辑。
  2. 高度耦合OrderService这个核心的交易服务,竟然直接依赖了短信、邮件、库存、分析等所有下游服务。任何一个下游服务的接口变更(比如deductStock方法增加了一个参数),都会导致OrderService被迫修改。
  3. 职责不清OrderService的核心职责是处理订单,但现在它却被迫了解所有“订单支付成功”后需要被通知的“干系人”,承担了“事件总线”的职责。

重构 —— 观察者模式的“解耦宣言”

要终结这场混乱,我们需要让OrderService从一个“主动的控制者”,变成一个“被动的发布者”。它不应该关心谁需要被通知,它只需要大喊一声:“嘿,这个订单支付成功了!”。谁关心这个消息,谁就自己来“收听”。这,就是观察者模式的用武之地。

image-20250814221815622

1. 定义“观察者”与“被观察者”的契约

// Observer: 观察者接口
public interface OrderStatusObserver {
    void onPaid(Order order);
}

// Subject: 被观察者(主题)接口
public abstract class OrderSubject {

    private final List<OrderStatusObserver> observers = new ArrayList<>();

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

    public void removeObserver(OrderStatusObserver observer) {
        observers.remove(observer);
    }

    // 通知所有观察者
    public void notifyPaid(Order order) {
        for (OrderStatusObserver observer : observers) {
            observer.onPaid(order);
        }
    }
}

2. 创建具体的“观察者”

库存服务观察者

@Component
public class InventoryObserver implements OrderStatusObserver {
    @Override
    public void onPaid(Order order) {
        System.out.println("[库存服务] 收到支付成功通知,开始扣减库存...");
        // inventoryService.deductStock(...);
    }
}

短信服务观察者

@Component
public class SmsObserver implements OrderStatusObserver {
    @Override
    public void onPaid(Order order) {
        System.out.println("[短信服务] 收到支付成功通知,准备发送短信...");
        // smsService.sendPaymentSuccessSms(...);
    }
}

3. 改造OrderService,让它成为一个纯粹的“发布者”

@Service
public class OrderService extends OrderSubject {
    
    // 在服务初始化时,注册所有观察者
    @Autowired
    public OrderService(List<OrderStatusObserver> observers) {
        observers.forEach(this::addObserver);
    }

    public void orderPaidSuccess(Order order) {
        // ... 订单自身状态变更的逻辑 ...
        System.out.println("订单 " + order.getId() + " 支付成功,状态更新完毕。");

        // 只做一件事:发布事件!
        System.out.println("--- 开始广播支付成功事件 ---");
        notifyPaid(order);
    }
}

优化点剖析

  • 开闭原则:未来增加“增加用户积分”功能,只需新增一个PointObserver类,实现OrderStatusObserver接口,并标记为@Component即可。OrderService一行代码都不用改!
  • 彻底解耦OrderService不再依赖任何具体的下游服务,它只依赖于OrderStatusObserver这个抽象。系统变成了“可插拔”的。
  • 职责单一OrderService只负责订单,InventoryObserver只负责库存。职责清晰,易于独立测试和维护。

从“单体”到“分布式”:观察者模式的终极升华

我们刚才的重构,完美地解决了单体应用内的耦合问题。但在现代微服务架构下,OrderServiceInventoryServiceSmsService很可能是独立部署的三个服务。它们之间的通信,跨越了进程和网络。此时,我们需要观察者模式的终极形态——消息队列(Message Queue)

消息队列,企业级的最佳实践

消息队列(如Kafka, RocketMQ, RabbitMQ)是观察者模式在分布式环境下的天然实现,也是企业中使用最广泛的方式。

  • 主题 (Subject) -> 消息生产者 (Producer) + 主题/Topic (Topic)
  • 观察者 (Observer) -> 消息消费者 (Consumer)
发布者(订单服务)
@Service
public class OrderService {
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    private static final String ORDER_PAID_TOPIC = "ORDER_PAID_TOPIC";

    public void orderPaidSuccess(Order order) {
        // ... 订单自身状态变更的逻辑 ...
        System.out.println("订单 " + order.getId() + " 支付成功,状态更新完毕。");

        // 1. 将订单信息序列化为JSON
        String orderJson = convertToJson(order);
        
        // 2. 发送消息到Kafka的Topic,然后立即返回
        System.out.println("--- 向Kafka主题 " + ORDER_PAID_TOPIC + " 发送消息 ---");
        kafkaTemplate.send(ORDER_PAID_TOPIC, orderJson);
    }
}
订阅者(库存服务)
@Service
public class InventoryService {
    
    // 监听指定的Kafka Topic
    @KafkaListener(topics = "ORDER_PAID_TOPIC", groupId = "inventory_group")
    public void handleOrderPaid(String orderJson) {
        // 1. 反序列化消息
        Order order = convertFromJson(orderJson);
        
        // 2. 执行自己的业务逻辑
        System.out.println("[库存微服务] 监听到消息,开始扣减库存 for order: " + order.getId());
        // deductStock(...);
    }
}
为什么说这是终极形态?
  1. 异步化OrderService发送消息后无需等待,立刻响应用户,系统吞吐量极大提升。
  2. 削峰填谷:大促期间,海量“支付成功”消息堆积在MQ中,下游的库存、短信服务可以按照自己的节奏平稳处理,避免被打垮。
  3. 无与伦比的解耦OrderService甚至不知道有几个下游服务、它们是死是活。新增一个“积分服务”?只需要让它也去订阅ORDER_PAID_TOPIC即可,OrderService毫不知情。

从手动实现到“框架级”事件驱动

在转向分布式之前,单体内我们也有更优雅的事件机制。我们刚才手动实现的观察者模式虽然可行,但在企业级应用中,我们有更强大、更优雅的武器。

一行注解的优雅——Spring的ApplicationEvent

Spring框架将观察者模式内置为其核心的事件机制。

  1. 定义一个事件对象,携带数据
public class OrderPaidEvent extends ApplicationEvent {
    public final Order order;
    public OrderPaidEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
}
  1. 发布者:注入ApplicationEventPublisher并发布事件
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher publisher;

    public void orderPaidSuccess(Order order) {
        System.out.println("订单 " + order.getId() + " 支付成功,状态更新完毕。");
        // 发布事件
        publisher.publishEvent(new OrderPaidEvent(this, order));
    }
}
  1. 观察者:用@EventListener注解,优雅地监听事件
@Component
public class InventoryService {
    @EventListener
    public void handleOrderPaid(OrderPaidEvent event) {
        System.out.println("[库存服务] 监听到事件,开始扣减库存 for order: " + event.order.getId());
    }
}

@Component
public class SmsService {
    @EventListener
    public void handleOrderPaid(OrderPaidEvent event) {
        System.out.println("[短信服务] 监听到事件,准备发送短信 for order: " + event.order.getId());
    }
}

设计巧思@EventListener让任何一个Spring Bean都能轻易地成为观察者,代码极其简洁。Spring还支持异步事件(@Async),轻松实现主流程与通知逻辑的异步解耦。

Google的解耦“瑞士军刀”——Guava的EventBus

场景:在一些非Spring环境,或者需要更轻量、更灵活的组件间通信时,GuavaEventBus是绝佳选择。它允许任意对象(POJO)之间进行发布-订阅,耦合度比Spring事件更低。

// 观察者
public class OrderListener {
    @Subscribe // Guava的注解
    public void onOrderPaid(Order order) {
        System.out.println("Guava EventBus: 订单 " + order.getId() + " 已支付");
    }
}

// 发布者
EventBus eventBus = new EventBus();
eventBus.register(new OrderListener());
eventBus.post(new Order()); // 发布事件

开源框架源码中观察者模式的“无所不在”

框架定位 (包/类/方法)作用解读
Spring Frameworkorg.springframework.context.ApplicationListenerSpring事件机制的基石。ApplicationContextSubject)在启动、关闭等关键节点会发布ContextRefreshedEvent等事件,所有ApplicationListenerObserver)都会收到通知,这是实现Spring Boot自动配置等“魔法”的关键。
Java Swing/AWTjava.awt.event.ActionListenerGUI编程的经典案例。JButtonSubject)是事件源,你可以为它添加多个ActionListenerObserver)。当按钮被点击时,它会遍历并调用所有ListeneractionPerformed方法。
Nettyio.netty.channel.ChannelPipelineNetty的ChannelPipelineChannelHandler机制,是观察者模式的一种高级、链式变体(更接近责任链)。当网络事件(如数据到达、连接断开)发生时,事件会在Pipeline中传播,并被各个Handler(观察者)依次处理。

用事件解耦,用监听扩展

  1. 识别“一”对“多”的依赖:当你发现一个对象的状态变化,需要通知多个其他对象时,就是观察者模式登场的信号。
  2. 发布者不应关心订阅者Subject(主题)的职责是维护订阅者列表和发布通知,它不应该知道任何Observer(观察者)的具体实现。
  3. 优先使用框架内置的事件机制:在Spring项目中,应首选ApplicationEvent@EventListener,它们更强大、配置更简单,且支持异步。
  4. 拥抱消息队列:在微服务架构下,消息队列是实现跨服务解耦、异步化和流量削峰的最佳实践,是观察者模式在分布式环境下的终极形态。
  5. 警惕内存泄漏:在手动实现观察者模式时,如果一个ObserverSubject持有引用,但它自身的生命周期已经结束,却没有从Subjectremove掉,就会导致内存泄漏。Spring的事件机制通过弱引用等方式优雅地处理了这个问题。

高手写的不是流程,而是响应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CV大魔王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值