观察者模式状态一变自动通知所有关注者订单状态变更要联动库存、物流、积分、短信四个模块——如何让订单代码不背这四个包袱观察者模式的解耦之道。一、痛点一个状态变更牵连 N 个模块订单的状态机很典型已支付 → 已发货 → 已完成。每个状态迁移都要触发一批联动动作支付成功 → 扣库存、发支付成功短信发货 → 创建物流单、发发货短信完成 → 发放积分、触发评价邀请。最直白的写法是在订单的状态变更方法里逐个调用publicvoidchangeStatus(StringorderId,Stringstatus){// 改状态// 调库存服务// 调物流服务// 调积分服务// 调短信服务// ...}问题显而易见订单核心代码变成了联动动作的堆砌场。每新增一个联动模块比如发放优惠券就要回来改这段代码——模块之间互相纠缠牵一发动全身。观察者模式的回答订单只负责广播我变了谁关心、各自怎么处理订单一概不知。二、实现被观察者 观察者接口 具体观察者第一步观察者接口状态变更时携带的信息不止一个状态字符串用一个事件对象承载更工程化// 订单状态变更事件publicclassOrderStatusChangedEvent{privatefinalStringorderId;privatefinalStringstatus;publicOrderStatusChangedEvent(StringorderId,Stringstatus){this.orderIdorderId;this.statusstatus;}publicStringorderId(){returnorderId;}publicStringstatus(){returnstatus;}}// 观察者接口对订单状态变更感兴趣的模块都实现它publicinterfaceOrderStatusListener{voidonStatusChanged(OrderStatusChangedEventevent);}第二步被观察者订单importjava.util.List;importjava.util.concurrent.CopyOnWriteArrayList;// 被观察者维护监听器列表状态变更时逐个通知publicclassOrderPublisher{// 写少读多用 CopyOnWriteArrayList 兼顾遍历安全privatefinalListOrderStatusListenerlistenersnewCopyOnWriteArrayList();publicvoidregister(OrderStatusListenerlistener){listeners.add(listener);}publicvoidunregister(OrderStatusListenerlistener){listeners.remove(listener);}publicvoidchangeStatus(StringorderId,StringnewStatus){notifyListeners(newOrderStatusChangedEvent(orderId,newStatus));}privatevoidnotifyListeners(OrderStatusChangedEventevent){for(OrderStatusListenerlistener:listeners){listener.onStatusChanged(event);}}}第三步具体观察者// 库存观察者支付成功扣减库存publicclassStockListenerimplementsOrderStatusListener{OverridepublicvoidonStatusChanged(OrderStatusChangedEventevent){if(PAID.equals(event.status())){System.out.println([库存] 订单 event.orderId() 已支付扣减库存);}}}// 物流观察者发货后创建物流单publicclassLogisticsListenerimplementsOrderStatusListener{OverridepublicvoidonStatusChanged(OrderStatusChangedEventevent){if(SHIPPED.equals(event.status())){System.out.println([物流] 订单 event.orderId() 已发货创建物流单);}}}// 积分观察者完成后发放积分publicclassPointsListenerimplementsOrderStatusListener{OverridepublicvoidonStatusChanged(OrderStatusChangedEventevent){if(COMPLETED.equals(event.status())){System.out.println([积分] 订单 event.orderId() 已完成发放积分);}}}// 短信观察者支付、发货两个节点都通知用户publicclassSmsListenerimplementsOrderStatusListener{OverridepublicvoidonStatusChanged(OrderStatusChangedEventevent){if(PAID.equals(event.status())||SHIPPED.equals(event.status())){System.out.println([短信] 订单 event.orderId() 状态event.status()发送通知短信);}}}第四步业务层组装publicclassOrderService{publicvoidchangeOrderStatus(StringorderId,Stringstatus){OrderPublisherordernewOrderPublisher();order.register(newStockListener());order.register(newLogisticsListener());order.register(newPointsListener());order.register(newSmsListener());order.changeStatus(orderId,status);}publicstaticvoidmain(String[]args){OrderServiceservicenewOrderService();service.changeOrderStatus(ORDER-1001,PAID);service.changeOrderStatus(ORDER-1001,SHIPPED);service.changeOrderStatus(ORDER-1001,COMPLETED);}}输出[库存] 订单 ORDER-1001 已支付扣减库存 [短信] 订单 ORDER-1001 状态PAID发送通知短信 [物流] 订单 ORDER-1001 已发货创建物流单 [短信] 订单 ORDER-1001 状态SHIPPED发送通知短信 [积分] 订单 ORDER-1001 已完成发放积分注意每个监听器自带状态过滤库存只关心 PAID物流只关心 SHIPPED——什么时候响应由观察者自己决定而不是由订单逐个判断。三、工程化上的三个注意点异步通知上面的notifyListeners是同步遍历短信这种外部调用会拖慢主流程。生产上应把通知投递到消息队列或线程池异步执行——状态变更本身先行返回联动动作慢慢消化异常隔离某个监听器抛异常不应阻断其他监听器。同步遍历时要 try-catch 单个调用异步投递则天然隔离现成轮子Java 的PropertyChangeSupport、Spring 的ApplicationEvent/EventListener都是观察者模式的成熟实现业务代码里通常不需要手写 Subject。四、实际收益订单代码瘦身状态变更方法里只剩改状态 广播不再感知任何联动模块的存在扩展零侵入新增优惠券发放联动只需新增一个监听器类并注册订单核心代码一行不改关注点各归其位库存、物流、积分、短信各自独立演进互不可见。小结观察者模式一句话被观察者只管广播观察者各取所需。它把一对多的通知从硬编码调用变成可插拔的订阅关系——新增联动方是加一个订阅者而不是改一处核心代码。
