[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-16":26,"\u002Farticles\u002Fdesign-pattern-16\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-16\u002Fadjacent":71,"\u002Fcategories":83,"render-design-pattern-16":109},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113932151245246465","design-pattern-16","设计模式修炼之路 ⑯ 观察者模式——事件订阅与广播","一对多依赖、订阅退订自由、生产环境三块拼图、Spring 事件机制、观察者 vs 发布订阅","2113897628365230081",1,3150,6331,16,"2024-07-07T14:57:29","> 行为型第三站。\n\n上一篇策略模式解决的是\"该用哪个算法\"，这一篇解决\"该通知谁\"。\n\n先看一段熟悉的代码。订单支付成功后要发短信、加积分、扣库存，你会在 OrderService 里怎么写？\n\n## 一、观察者模式是什么\n\nGoF 的原始定义：\n\n> Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.\n> （在对象间定义一对多依赖，当一个对象状态改变时，所有依赖它的对象都会自动收到通知并更新。）\n\n翻译成人话：**一个对象有事发生，就往一份\"订阅名单\"里群发通知；谁关心这事，谁去名单上登记。**\n\n四个角色：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>角色\u003C\u002Fth>\u003Cth>干什么\u003C\u002Fth>\u003Cth>本文对应\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>Subject（目标\u002F被观察者）\u003C\u002Ftd>\u003Ctd>维护订阅名单，状态变化时挨个通知\u003C\u002Ftd>\u003Ctd>OrderSubject\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>Observer（观察者接口）\u003C\u002Ftd>\u003Ctd>收到通知后干什么，统一契约\u003C\u002Ftd>\u003Ctd>OrderObserver\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>ConcreteObserver（具体观察者）\u003C\u002Ftd>\u003Ctd>短信、积分、库存、物流，各管一摊\u003C\u002Ftd>\u003Ctd>四个 XxxObserver\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>（业务方法）\u003C\u002Ftd>\u003Ctd>触发点：支付成功这件事本身\u003C\u002Ftd>\u003Ctd>paySuccess()\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n最贴切的类比是**公众号**：你关注了某个号（attach），号主发新文章时你自动收到推送（notify）；不感兴趣了取关（detach），以后推什么都不打扰你。号主从不知道订阅者是谁、有多少人，他只管发文。\n\n和前几篇的关系：⑮ 策略模式是 Context 持有**一个**策略、指哪打哪；观察者是 Subject 持有**一串**观察者、有事全广播。一个一对一，一个一对多——正好接着讲。\n\n## 二、优点与缺点\n\n**优点：**\n\n1. **彻底解耦**。OrderSubject 只认识 OrderObserver 接口，不知道短信怎么发、积分怎么加。下游换实现、换类名，上游一行不动。\n2. **对扩展开放**。新下游接入 = 写个类 + attach 一行；甚至这行都不用写（后面 Spring 部分见）。\n3. **动态订阅退订**。观察者名单是运行时的，随时增减——测试里短信退订后广播立刻少一家，就是这意思。\n4. **广播效率**。一次事件触发 N 个动作，触发点只有一行循环。\n\n**缺点：**\n\n1. **观察者之间顺序不可控**。积分要在库存扣完之后执行？观察者模式不保证顺序（本例按注册顺序算运气好）。有顺序依赖就别用观察者，那是 ⑰ 责任链的地盘。\n2. **通知风暴**。A 通知 B，B 状态变化又通知 A……链式反应没设计好就是死循环，CPU 直接拉满。\n3. **内存泄漏风险**。Subject 持有 Observer 的强引用，detach 忘了调，观察者永远无法被 GC——Swing 程序内存泄漏的老病灶。\n4. **一个观察者抛异常，广播中断**。本例循环里没有 try-catch，短信挂了库存就收不到通知。生产代码必须给每个观察者的调用套上异常隔离。\n\n## 三、什么时候用\n\n判断标准：**一件事发生后，有多个互不相识的下游关心，且这份名单会变。**\n\n- **业务事件广播**：支付成功、注册成功、审批通过 → 短信\u002F积分\u002F风控\u002F埋点\n- **对象状态联动**：表格数据变了，柱状图、折线图、导出模块都要刷新（MVC 里 View 监听 Model）\n- **跨模块解耦**：订单模块不想认识物流模块，但物流想感知订单\n\n反面对照：下游固定就一个、或者下游之间有先后顺序、或者需要拿到处理结果——都不适合观察者，那是策略（⑮）、责任链（⑰）或者直接调用的场景。\n\n## 四、实现方式：把\"点对点调用\"改成\"广播订阅\"\n\n### 4.1 反面教材：下单后依次调三个下游\n\n```java\npublic class BadOrderService {\n\n    public void paySuccess(String orderId) {\n        System.out.println(\"[订单] 订单 \" + orderId + \" 支付成功\");\n        System.out.println(\"  [短信] 尊敬的用户，订单 \" + orderId + \" 已支付\");\n        System.out.println(\"  [积分] 订单 \" + orderId + \" 增加 100 积分\");\n        System.out.println(\"  [库存] 扣减订单 \" + orderId + \" 对应库存\");\n        \u002F\u002F 下个月要接物流、接风控、接数据埋点……\n        \u002F\u002F 每接一个下游，这个方法就得改一次，回归测试全量重跑\n    }\n}\n```\n\nOrderService 认识了三个下游：发短信的、加积分的、扣库存的。每接一个新下游，这个方法就得改一次——和 ⑮ 的 if-else 巨无霸是同一种病：**把\"变化的部分\"焊死在\"稳定的部分\"里**。区别只是策略模式里变化的是算法分支，这里变化的是下游名单。\n\n### 4.2 正解：Subject 拿名单，Observer 各干各的\n\n第一步，观察者接口——\"收到订单支付通知后干什么\"：\n\n```java\npublic interface OrderObserver {\n\n    void onOrderPaid(String orderId);\n}\n```\n\n第二步，被观察者——维护名单 + 业务方法里广播：\n\n```java\npublic class OrderSubject {\n\n    private final List\u003COrderObserver> observers = new ArrayList\u003C>();\n\n    \u002F\u002F 订阅：观察者注册进名单\n    public void attach(OrderObserver observer) {\n        observers.add(observer);\n    }\n\n    \u002F\u002F 退订：从名单里移除，之后的广播不再收到\n    public void detach(OrderObserver observer) {\n        observers.remove(observer);\n    }\n\n    public void paySuccess(String orderId) {\n        System.out.println(\"[订单] 订单 \" + orderId + \" 支付成功，开始广播\");\n        for (OrderObserver observer : observers) {\n            observer.onOrderPaid(orderId);\n        }\n    }\n}\n```\n\n第三步，三个具体观察者：\n\n```java\npublic class SmsObserver implements OrderObserver {\n\n    @Override\n    public void onOrderPaid(String orderId) {\n        System.out.println(\"  [短信] 尊敬的用户，订单 \" + orderId + \" 已支付\");\n    }\n}\n\npublic class PointsObserver implements OrderObserver {\n\n    @Override\n    public void onOrderPaid(String orderId) {\n        System.out.println(\"  [积分] 订单 \" + orderId + \" 增加 100 积分\");\n    }\n}\n\npublic class StockObserver implements OrderObserver {\n\n    @Override\n    public void onOrderPaid(String orderId) {\n        System.out.println(\"  [库存] 扣减订单 \" + orderId + \" 对应库存\");\n    }\n}\n```\n\n测试类跑一遍——重点看后半段，物流中途接入、短信中途退订：\n\n```java\npublic class ObserverTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：下单后直接调三个下游 ==========\");\n        new BadOrderService().paySuccess(\"1001\");\n\n        System.out.println();\n        System.out.println(\"========== 正解：观察者模式 ==========\");\n        OrderSubject subject = new OrderSubject();\n        SmsObserver sms = new SmsObserver();\n        subject.attach(sms);\n        subject.attach(new PointsObserver());\n        subject.attach(new StockObserver());\n        subject.paySuccess(\"2001\");\n\n        System.out.println();\n        System.out.println(\"-- 新下游「物流」接入：不改 OrderSubject，注册即生效 --\");\n        subject.attach(new LogisticsObserver());\n        subject.paySuccess(\"3001\");\n\n        System.out.println();\n        System.out.println(\"-- 短信观察者退订（detach），广播立刻少一家 --\");\n        subject.detach(sms);\n        subject.paySuccess(\"4001\");\n    }\n}\n```\n\n运行输出：\n\n```text\n========== 反面教材：下单后直接调三个下游 ==========\n[订单] 订单 1001 支付成功\n  [短信] 尊敬的用户，订单 1001 已支付\n  [积分] 订单 1001 增加 100 积分\n  [库存] 扣减订单 1001 对应库存\n\n========== 正解：观察者模式 ==========\n[订单] 订单 2001 支付成功，开始广播\n  [短信] 尊敬的用户，订单 2001 已支付\n  [积分] 订单 2001 增加 100 积分\n  [库存] 扣减订单 2001 对应库存\n\n-- 新下游「物流」接入：不改 OrderSubject，注册即生效 --\n[订单] 订单 3001 支付成功，开始广播\n  [短信] 尊敬的用户，订单 3001 已支付\n  [积分] 订单 3001 增加 100 积分\n  [库存] 扣减订单 3001 对应库存\n  [物流] 通知仓库拣货发货：订单 3001\n\n-- 短信观察者退订（detach），广播立刻少一家 --\n[订单] 订单 4001 支付成功，开始广播\n  [积分] 订单 4001 增加 100 积分\n  [库存] 扣减订单 4001 对应库存\n  [物流] 通知仓库拣货发货：订单 4001\n```\n\n### 4.3 三处细节咂摸一下\n\n**细节一：订单 3001 多了一行物流，OrderSubject 没改一行代码。**写 LogisticsObserver、attach，广播自然多一家。开闭原则的达成方式和 ⑮ 一模一样：新变化 = 新类，不是改旧类。\n\n**细节二：订单 4001 少了短信，也是运行时动态生效。**观察者名单是活的，订阅退订都是运行期行为。这是\"依次调用三个方法\"永远做不到的。\n\n**细节三：广播循环只有一行 `observer.onOrderPaid(orderId)`。**Subject 对观察者的一切认知浓缩在这行里：它是个 OrderObserver，出了事我调它。至于它是谁、要干什么、干砸了怎么办——Subject 一概不知，也不想知道。这行代码就是\"依赖倒置\"的最小标本：依赖抽象，不依赖具体。\n\n### 4.4 生产环境要补的三块拼图\n\n教程版到此为止，生产代码还要补三件事：\n\n**① 异常隔离**——一个观察者炸了不能连累其他观察者：\n\n```java\nfor (OrderObserver observer : observers) {\n    try {\n        observer.onOrderPaid(orderId);\n    } catch (Exception e) {\n        log.error(\"观察者执行失败: {}\", observer.getClass().getSimpleName(), e);\n    }\n}\n```\n\n**② 并发安全**——如果广播的同时有别的线程 attach\u002Fdetach，ArrayList 会抛 ConcurrentModificationException。监听器列表的经典选择是 `CopyOnWriteArrayList`（读多写少场景正合适），Swing 里则用专门的 `EventListenerList`。\n\n**③ 生命周期管理**——长生命周期 Subject 挂着短生命周期观察者，用完必须 detach，否则就是第二节说的内存泄漏。Java 里也可以用弱引用（`WeakReference`）兜底，观察者没别的强引用时自动从名单里\"失效\"。\n\n## 五、在框架里找它\n\n**JDK 的 Observable——一个反面教材式的官方实现。**java.util.Observable 从 JDK 1.0 就有（Observable 类 + Observer 接口），但 JDK 9 起被标记 @Deprecated。为什么？它是类不是接口，想观察就得继承它，把唯一的继承名额烧了（这个理由 ⑭ 里也吐槽过）；setChanged\u002FnotifyObservers 的状态耦合设计也别扭。**官方亲手示范了\"观察者思想的正确性\"和\"API 设计的失败\"可以并存**——思想用你的，代码自己写。\n\n**Swing\u002FAWT 事件监听。**每个按钮都是被观察者，每个 ActionListener 都是观察者：\n\n```java\nbutton.addActionListener(e -> System.out.println(\"被点了\"));\n```\n\nawt 的事件体系把观察者模式铺到了桌面编程的每个角落。\n\n**Spring 的事件机制——Java 后端最常用形态。**发布方注入 ApplicationEventPublisher，一行发布；接收方一个注解订阅，连 attach 都省了：\n\n```java\n\u002F\u002F 发布：订单服务里\npublisher.publishEvent(new OrderPaidEvent(orderId));\n\n\u002F\u002F 订阅：物流模块里\n@EventListener\npublic void onOrderPaid(OrderPaidEvent event) {\n    logisticsService.arrangeShipment(event.getOrderId());\n}\n```\n\n默认同步执行（发布线程里跑监听器），想异步加 `@Async`；想保证事务提交后才通知，用 `@TransactionalEventListener`——同步\u002F异步\u002F事务边界三个开关，把教程版观察者的坑全填了。\n\n**Guava EventBus。**把\"接口约束 + 名单维护\"简化成\"一个注解 + 一个 register\"：\n\n```java\neventBus.register(new LogisticsObserver());  \u002F\u002F 无需实现任何接口\n\n@Subscribe\npublic void onOrderPaid(OrderPaidEvent event) { ... }\n```\n\n按事件类型自动分发，Spring 的 @EventListener 明显师承于此。\n\n**消息队列——观察者的分布式形态。**RocketMQ\u002FKafka 的发布订阅，把\"Subject 持有 Observer 引用\"换成\"大家都只连 Broker\"：生产者发消息，N 个消费组各自消费。观察者模式的解耦思想放大到跨进程，就成了 MQ。第六节接着这个话头展开。\n\n## 六、观察者 vs 发布订阅——面试高频题\n\n很多文章把两个词混着用，面试官却爱较真。区别一句话：**看有没有中间人。**\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>\u003C\u002Fth>\u003Cth>观察者模式\u003C\u002Fth>\u003Cth>发布订阅模式\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>结构\u003C\u002Ftd>\u003Ctd>Subject **直接持有** Observer 引用\u003C\u002Ftd>\u003Ctd>发布者和订阅者**互不相识**，中间隔着事件总线\u002FBroker\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>耦合\u003C\u002Ftd>\u003Ctd>松耦合，但同在一个进程里\u003C\u002Ftd>\u003Ctd>完全解耦，可跨进程、跨机器\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>时机\u003C\u002Ftd>\u003Ctd>通常同步，通知完才返回\u003C\u002Ftd>\u003Ctd>通常异步，发完即走\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>典型\u003C\u002Ftd>\u003Ctd>本篇 OrderSubject、Swing\u003C\u002Ftd>\u003Ctd>MQ、Redis pub\u002Fsub、Spring 事件（半个）\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n本篇手写的 GoF 版是纯观察者；Spring 事件因为引入了 ApplicationEventPublisher 这个\"隐形中间人\"，已经带一点发布订阅的意思；到了 MQ 就是彻底的发布订阅。**观察者是设计模式，发布订阅是架构风格**——这么答，面试官一般会点头。\n\n## 写在最后\n\n观察者模式一句话总结：**一方有事，名单广播；订阅自愿，退订自由。**\n\n它和策略模式合起来覆盖了行为型的两大解耦方向：⑮ 解耦\"用哪个算法\"（一对一），⑯ 解耦\"通知哪些下游\"（一对多）。下一篇 ⑰ 责任链模式补上第三种：**请求交给谁处理**——报销单从你手里递出去，组长批不过找经理，经理批不过找总监，请求沿着链条一站一站传下去，谁有权限谁处理。审批流的经典实现，下一篇见。","\u003Cblockquote>\n \u003Cp>行为型第三站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇策略模式解决的是\"该用哪个算法\"，这一篇解决\"该通知谁\"。\u003C\u002Fp>\n\u003Cp>先看一段熟悉的代码。订单支付成功后要发短信、加积分、扣库存，你会在 OrderService 里怎么写？\u003C\u002Fp>\n\u003Ch2>一、观察者模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. （在对象间定义一对多依赖，当一个对象状态改变时，所有依赖它的对象都会自动收到通知并更新。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>一个对象有事发生，就往一份\"订阅名单\"里群发通知；谁关心这事，谁去名单上登记。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>四个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Subject（目标\u002F被观察者）\u003C\u002Ftd>\n   \u003Ctd>维护订阅名单，状态变化时挨个通知\u003C\u002Ftd>\n   \u003Ctd>OrderSubject\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Observer（观察者接口）\u003C\u002Ftd>\n   \u003Ctd>收到通知后干什么，统一契约\u003C\u002Ftd>\n   \u003Ctd>OrderObserver\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteObserver（具体观察者）\u003C\u002Ftd>\n   \u003Ctd>短信、积分、库存、物流，各管一摊\u003C\u002Ftd>\n   \u003Ctd>四个 XxxObserver\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>（业务方法）\u003C\u002Ftd>\n   \u003Ctd>触发点：支付成功这件事本身\u003C\u002Ftd>\n   \u003Ctd>paySuccess()\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>最贴切的类比是\u003Cstrong>公众号\u003C\u002Fstrong>：你关注了某个号（attach），号主发新文章时你自动收到推送（notify）；不感兴趣了取关（detach），以后推什么都不打扰你。号主从不知道订阅者是谁、有多少人，他只管发文。\u003C\u002Fp>\n\u003Cp>和前几篇的关系：⑮ 策略模式是 Context 持有\u003Cstrong>一个\u003C\u002Fstrong>策略、指哪打哪；观察者是 Subject 持有\u003Cstrong>一串\u003C\u002Fstrong>观察者、有事全广播。一个一对一，一个一对多——正好接着讲。\u003C\u002Fp>\n\u003Ch2>二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>彻底解耦\u003C\u002Fstrong>。OrderSubject 只认识 OrderObserver 接口，不知道短信怎么发、积分怎么加。下游换实现、换类名，上游一行不动。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对扩展开放\u003C\u002Fstrong>。新下游接入 = 写个类 + attach 一行；甚至这行都不用写（后面 Spring 部分见）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>动态订阅退订\u003C\u002Fstrong>。观察者名单是运行时的，随时增减——测试里短信退订后广播立刻少一家，就是这意思。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>广播效率\u003C\u002Fstrong>。一次事件触发 N 个动作，触发点只有一行循环。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>观察者之间顺序不可控\u003C\u002Fstrong>。积分要在库存扣完之后执行？观察者模式不保证顺序（本例按注册顺序算运气好）。有顺序依赖就别用观察者，那是 ⑰ 责任链的地盘。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>通知风暴\u003C\u002Fstrong>。A 通知 B，B 状态变化又通知 A……链式反应没设计好就是死循环，CPU 直接拉满。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>内存泄漏风险\u003C\u002Fstrong>。Subject 持有 Observer 的强引用，detach 忘了调，观察者永远无法被 GC——Swing 程序内存泄漏的老病灶。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>一个观察者抛异常，广播中断\u003C\u002Fstrong>。本例循环里没有 try-catch，短信挂了库存就收不到通知。生产代码必须给每个观察者的调用套上异常隔离。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>一件事发生后，有多个互不相识的下游关心，且这份名单会变。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>业务事件广播\u003C\u002Fstrong>：支付成功、注册成功、审批通过 → 短信\u002F积分\u002F风控\u002F埋点\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对象状态联动\u003C\u002Fstrong>：表格数据变了，柱状图、折线图、导出模块都要刷新（MVC 里 View 监听 Model）\u003C\u002Fli>\n \u003Cli>\u003Cstrong>跨模块解耦\u003C\u002Fstrong>：订单模块不想认识物流模块，但物流想感知订单\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：下游固定就一个、或者下游之间有先后顺序、或者需要拿到处理结果——都不适合观察者，那是策略（⑮）、责任链（⑰）或者直接调用的场景。\u003C\u002Fp>\n\u003Ch2>四、实现方式：把\"点对点调用\"改成\"广播订阅\"\u003C\u002Fh2>\n\u003Ch3>4.1 反面教材：下单后依次调三个下游\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-java\">public class BadOrderService {\n\n    public void paySuccess(String orderId) {\n        System.out.println(\"[订单] 订单 \" + orderId + \" 支付成功\");\n        System.out.println(\"  [短信] 尊敬的用户，订单 \" + orderId + \" 已支付\");\n        System.out.println(\"  [积分] 订单 \" + orderId + \" 增加 100 积分\");\n        System.out.println(\"  [库存] 扣减订单 \" + orderId + \" 对应库存\");\n        \u002F\u002F 下个月要接物流、接风控、接数据埋点……\n        \u002F\u002F 每接一个下游，这个方法就得改一次，回归测试全量重跑\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>OrderService 认识了三个下游：发短信的、加积分的、扣库存的。每接一个新下游，这个方法就得改一次——和 ⑮ 的 if-else 巨无霸是同一种病：\u003Cstrong>把\"变化的部分\"焊死在\"稳定的部分\"里\u003C\u002Fstrong>。区别只是策略模式里变化的是算法分支，这里变化的是下游名单。\u003C\u002Fp>\n\u003Ch3>4.2 正解：Subject 拿名单，Observer 各干各的\u003C\u002Fh3>\n\u003Cp>第一步，观察者接口——\"收到订单支付通知后干什么\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public interface OrderObserver {\n\n    void onOrderPaid(String orderId);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，被观察者——维护名单 + 业务方法里广播：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class OrderSubject {\n\n    private final List&lt;OrderObserver&gt; observers = new ArrayList&lt;&gt;();\n\n    \u002F\u002F 订阅：观察者注册进名单\n    public void attach(OrderObserver observer) {\n        observers.add(observer);\n    }\n\n    \u002F\u002F 退订：从名单里移除，之后的广播不再收到\n    public void detach(OrderObserver observer) {\n        observers.remove(observer);\n    }\n\n    public void paySuccess(String orderId) {\n        System.out.println(\"[订单] 订单 \" + orderId + \" 支付成功，开始广播\");\n        for (OrderObserver observer : observers) {\n            observer.onOrderPaid(orderId);\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第三步，三个具体观察者：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class SmsObserver implements OrderObserver {\n\n    @Override\n    public void onOrderPaid(String orderId) {\n        System.out.println(\"  [短信] 尊敬的用户，订单 \" + orderId + \" 已支付\");\n    }\n}\n\npublic class PointsObserver implements OrderObserver {\n\n    @Override\n    public void onOrderPaid(String orderId) {\n        System.out.println(\"  [积分] 订单 \" + orderId + \" 增加 100 积分\");\n    }\n}\n\npublic class StockObserver implements OrderObserver {\n\n    @Override\n    public void onOrderPaid(String orderId) {\n        System.out.println(\"  [库存] 扣减订单 \" + orderId + \" 对应库存\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试类跑一遍——重点看后半段，物流中途接入、短信中途退订：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class ObserverTest {\n\n    public static void main(String[] args) {\n        System.out.println(\"========== 反面教材：下单后直接调三个下游 ==========\");\n        new BadOrderService().paySuccess(\"1001\");\n\n        System.out.println();\n        System.out.println(\"========== 正解：观察者模式 ==========\");\n        OrderSubject subject = new OrderSubject();\n        SmsObserver sms = new SmsObserver();\n        subject.attach(sms);\n        subject.attach(new PointsObserver());\n        subject.attach(new StockObserver());\n        subject.paySuccess(\"2001\");\n\n        System.out.println();\n        System.out.println(\"-- 新下游「物流」接入：不改 OrderSubject，注册即生效 --\");\n        subject.attach(new LogisticsObserver());\n        subject.paySuccess(\"3001\");\n\n        System.out.println();\n        System.out.println(\"-- 短信观察者退订（detach），广播立刻少一家 --\");\n        subject.detach(sms);\n        subject.paySuccess(\"4001\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-text\">========== 反面教材：下单后直接调三个下游 ==========\n[订单] 订单 1001 支付成功\n  [短信] 尊敬的用户，订单 1001 已支付\n  [积分] 订单 1001 增加 100 积分\n  [库存] 扣减订单 1001 对应库存\n\n========== 正解：观察者模式 ==========\n[订单] 订单 2001 支付成功，开始广播\n  [短信] 尊敬的用户，订单 2001 已支付\n  [积分] 订单 2001 增加 100 积分\n  [库存] 扣减订单 2001 对应库存\n\n-- 新下游「物流」接入：不改 OrderSubject，注册即生效 --\n[订单] 订单 3001 支付成功，开始广播\n  [短信] 尊敬的用户，订单 3001 已支付\n  [积分] 订单 3001 增加 100 积分\n  [库存] 扣减订单 3001 对应库存\n  [物流] 通知仓库拣货发货：订单 3001\n\n-- 短信观察者退订（detach），广播立刻少一家 --\n[订单] 订单 4001 支付成功，开始广播\n  [积分] 订单 4001 增加 100 积分\n  [库存] 扣减订单 4001 对应库存\n  [物流] 通知仓库拣货发货：订单 4001\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>**细节一：订单 3001 多了一行物流，OrderSubject 没改一行代码。**写 LogisticsObserver、attach，广播自然多一家。开闭原则的达成方式和 ⑮ 一模一样：新变化 = 新类，不是改旧类。\u003C\u002Fp>\n\u003Cp>**细节二：订单 4001 少了短信，也是运行时动态生效。**观察者名单是活的，订阅退订都是运行期行为。这是\"依次调用三个方法\"永远做不到的。\u003C\u002Fp>\n\u003Cp>**细节三：广播循环只有一行 \u003Ccode>observer.onOrderPaid(orderId)\u003C\u002Fcode>。**Subject 对观察者的一切认知浓缩在这行里：它是个 OrderObserver，出了事我调它。至于它是谁、要干什么、干砸了怎么办——Subject 一概不知，也不想知道。这行代码就是\"依赖倒置\"的最小标本：依赖抽象，不依赖具体。\u003C\u002Fp>\n\u003Ch3>4.4 生产环境要补的三块拼图\u003C\u002Fh3>\n\u003Cp>教程版到此为止，生产代码还要补三件事：\u003C\u002Fp>\n\u003Cp>\u003Cstrong>① 异常隔离\u003C\u002Fstrong>——一个观察者炸了不能连累其他观察者：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">for (OrderObserver observer : observers) {\n    try {\n        observer.onOrderPaid(orderId);\n    } catch (Exception e) {\n        log.error(\"观察者执行失败: {}\", observer.getClass().getSimpleName(), e);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>② 并发安全\u003C\u002Fstrong>——如果广播的同时有别的线程 attach\u002Fdetach，ArrayList 会抛 ConcurrentModificationException。监听器列表的经典选择是 \u003Ccode>CopyOnWriteArrayList\u003C\u002Fcode>（读多写少场景正合适），Swing 里则用专门的 \u003Ccode>EventListenerList\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>③ 生命周期管理\u003C\u002Fstrong>——长生命周期 Subject 挂着短生命周期观察者，用完必须 detach，否则就是第二节说的内存泄漏。Java 里也可以用弱引用（\u003Ccode>WeakReference\u003C\u002Fcode>）兜底，观察者没别的强引用时自动从名单里\"失效\"。\u003C\u002Fp>\n\u003Ch2>五、在框架里找它\u003C\u002Fh2>\n\u003Cp>**JDK 的 Observable——一个反面教材式的官方实现。**java.util.Observable 从 JDK 1.0 就有（Observable 类 + Observer 接口），但 JDK 9 起被标记 @Deprecated。为什么？它是类不是接口，想观察就得继承它，把唯一的继承名额烧了（这个理由 ⑭ 里也吐槽过）；setChanged\u002FnotifyObservers 的状态耦合设计也别扭。\u003Cstrong>官方亲手示范了\"观察者思想的正确性\"和\"API 设计的失败\"可以并存\u003C\u002Fstrong>——思想用你的，代码自己写。\u003C\u002Fp>\n\u003Cp>**Swing\u002FAWT 事件监听。**每个按钮都是被观察者，每个 ActionListener 都是观察者：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">button.addActionListener(e -&gt; System.out.println(\"被点了\"));\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>awt 的事件体系把观察者模式铺到了桌面编程的每个角落。\u003C\u002Fp>\n\u003Cp>**Spring 的事件机制——Java 后端最常用形态。**发布方注入 ApplicationEventPublisher，一行发布；接收方一个注解订阅，连 attach 都省了：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">\u002F\u002F 发布：订单服务里\npublisher.publishEvent(new OrderPaidEvent(orderId));\n\n\u002F\u002F 订阅：物流模块里\n@EventListener\npublic void onOrderPaid(OrderPaidEvent event) {\n    logisticsService.arrangeShipment(event.getOrderId());\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>默认同步执行（发布线程里跑监听器），想异步加 \u003Ccode>@Async\u003C\u002Fcode>；想保证事务提交后才通知，用 \u003Ccode>@TransactionalEventListener\u003C\u002Fcode>——同步\u002F异步\u002F事务边界三个开关，把教程版观察者的坑全填了。\u003C\u002Fp>\n\u003Cp>**Guava EventBus。**把\"接口约束 + 名单维护\"简化成\"一个注解 + 一个 register\"：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">eventBus.register(new LogisticsObserver());  \u002F\u002F 无需实现任何接口\n\n@Subscribe\npublic void onOrderPaid(OrderPaidEvent event) { ... }\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>按事件类型自动分发，Spring 的 @EventListener 明显师承于此。\u003C\u002Fp>\n\u003Cp>**消息队列——观察者的分布式形态。**RocketMQ\u002FKafka 的发布订阅，把\"Subject 持有 Observer 引用\"换成\"大家都只连 Broker\"：生产者发消息，N 个消费组各自消费。观察者模式的解耦思想放大到跨进程，就成了 MQ。第六节接着这个话头展开。\u003C\u002Fp>\n\u003Ch2>六、观察者 vs 发布订阅——面试高频题\u003C\u002Fh2>\n\u003Cp>很多文章把两个词混着用，面试官却爱较真。区别一句话：\u003Cstrong>看有没有中间人。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>观察者模式\u003C\u002Fth>\n   \u003Cth>发布订阅模式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>结构\u003C\u002Ftd>\n   \u003Ctd>Subject **直接持有** Observer 引用\u003C\u002Ftd>\n   \u003Ctd>发布者和订阅者**互不相识**，中间隔着事件总线\u002FBroker\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>耦合\u003C\u002Ftd>\n   \u003Ctd>松耦合，但同在一个进程里\u003C\u002Ftd>\n   \u003Ctd>完全解耦，可跨进程、跨机器\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>时机\u003C\u002Ftd>\n   \u003Ctd>通常同步，通知完才返回\u003C\u002Ftd>\n   \u003Ctd>通常异步，发完即走\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型\u003C\u002Ftd>\n   \u003Ctd>本篇 OrderSubject、Swing\u003C\u002Ftd>\n   \u003Ctd>MQ、Redis pub\u002Fsub、Spring 事件（半个）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>本篇手写的 GoF 版是纯观察者；Spring 事件因为引入了 ApplicationEventPublisher 这个\"隐形中间人\"，已经带一点发布订阅的意思；到了 MQ 就是彻底的发布订阅。\u003Cstrong>观察者是设计模式，发布订阅是架构风格\u003C\u002Fstrong>——这么答，面试官一般会点头。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>观察者模式一句话总结：\u003Cstrong>一方有事，名单广播；订阅自愿，退订自由。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>它和策略模式合起来覆盖了行为型的两大解耦方向：⑮ 解耦\"用哪个算法\"（一对一），⑯ 解耦\"通知哪些下游\"（一对多）。下一篇 ⑰ 责任链模式补上第三种：\u003Cstrong>请求交给谁处理\u003C\u002Fstrong>——报销单从你手里递出去，组长批不过找经理，经理批不过找总监，请求沿着链条一站一站传下去，谁有权限谁处理。审批流的经典实现，下一篇见。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,63],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113933842262790145","design-pattern-19","设计模式修炼之路 ⑲ 状态模式——状态驱动行为","状态驱动行为、流转规则住进状态家、Thread 状态机、状态 vs 策略终局对决",1320,6715,17,"2024-09-02T16:09:32",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},"2113931965261418497","design-pattern-15","设计模式修炼之路 ⑮ 策略模式——if-else 的解药","一个分支一个类、工厂加 Map 连选择处都不写 if-else、Spring 自动收集、线程池拒绝策略",1681,7953,20,"2024-06-16T18:10:44",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":52,"publishedAt":70},"2113934247046680578","design-pattern-22","设计模式修炼之路 ㉒ 中介者模式——网状依赖变星型","网状依赖变星型、N×(N-1) 收成 N 条、聊天室聚餐事故、中介者 vs 观察者",1550,6591,"2024-11-01T18:45:07",{"code":4,"message":5,"data":72},{"prev":73,"next":74},{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},{"id":75,"slug":76,"title":77,"summary":78,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":79,"likeCount":4,"wordCount":80,"readingMinutes":81,"publishedAt":82},"2113932208883372034","design-pattern-17","设计模式修炼之路 ⑰ 责任链模式——请求沿链条传递","客户端只认识链头、节点自治三段式、纯链与管线式、从 Servlet Filter 到 Netty Pipeline",3708,5950,15,"2024-07-23T20:24:49",{"code":4,"message":5,"data":84},[85,91,96,102],{"id":86,"name":87,"slug":88,"description":89,"sortOrder":4,"articleCount":90},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":92,"slug":93,"description":94,"sortOrder":4,"articleCount":95},"设计模式","design-pattern","设计模式系列","34",{"id":97,"name":98,"slug":99,"description":100,"sortOrder":33,"articleCount":101},"1946000000000000002","随笔","essay","生活与思考","0",{"id":103,"name":104,"slug":105,"description":106,"sortOrder":107,"articleCount":108},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":110,"toc":111},"\u003Cblockquote>\n \u003Cp>行为型第三站。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>上一篇策略模式解决的是\"该用哪个算法\"，这一篇解决\"该通知谁\"。\u003C\u002Fp>\n\u003Cp>先看一段熟悉的代码。订单支付成功后要发短信、加积分、扣库存，你会在 OrderService 里怎么写？\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、观察者模式是什么\u003C\u002Fh2>\n\u003Cp>GoF 的原始定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. （在对象间定义一对多依赖，当一个对象状态改变时，所有依赖它的对象都会自动收到通知并更新。）\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>一个对象有事发生，就往一份\"订阅名单\"里群发通知；谁关心这事，谁去名单上登记。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>四个角色：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>角色\u003C\u002Fth>\n   \u003Cth>干什么\u003C\u002Fth>\n   \u003Cth>本文对应\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>Subject（目标\u002F被观察者）\u003C\u002Ftd>\n   \u003Ctd>维护订阅名单，状态变化时挨个通知\u003C\u002Ftd>\n   \u003Ctd>OrderSubject\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>Observer（观察者接口）\u003C\u002Ftd>\n   \u003Ctd>收到通知后干什么，统一契约\u003C\u002Ftd>\n   \u003Ctd>OrderObserver\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>ConcreteObserver（具体观察者）\u003C\u002Ftd>\n   \u003Ctd>短信、积分、库存、物流，各管一摊\u003C\u002Ftd>\n   \u003Ctd>四个 XxxObserver\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>（业务方法）\u003C\u002Ftd>\n   \u003Ctd>触发点：支付成功这件事本身\u003C\u002Ftd>\n   \u003Ctd>paySuccess()\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>最贴切的类比是\u003Cstrong>公众号\u003C\u002Fstrong>：你关注了某个号（attach），号主发新文章时你自动收到推送（notify）；不感兴趣了取关（detach），以后推什么都不打扰你。号主从不知道订阅者是谁、有多少人，他只管发文。\u003C\u002Fp>\n\u003Cp>和前几篇的关系：⑮ 策略模式是 Context 持有\u003Cstrong>一个\u003C\u002Fstrong>策略、指哪打哪；观察者是 Subject 持有\u003Cstrong>一串\u003C\u002Fstrong>观察者、有事全广播。一个一对一，一个一对多——正好接着讲。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、优点与缺点\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>优点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>彻底解耦\u003C\u002Fstrong>。OrderSubject 只认识 OrderObserver 接口，不知道短信怎么发、积分怎么加。下游换实现、换类名，上游一行不动。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对扩展开放\u003C\u002Fstrong>。新下游接入 = 写个类 + attach 一行；甚至这行都不用写（后面 Spring 部分见）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>动态订阅退订\u003C\u002Fstrong>。观察者名单是运行时的，随时增减——测试里短信退订后广播立刻少一家，就是这意思。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>广播效率\u003C\u002Fstrong>。一次事件触发 N 个动作，触发点只有一行循环。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>\u003Cstrong>缺点：\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>观察者之间顺序不可控\u003C\u002Fstrong>。积分要在库存扣完之后执行？观察者模式不保证顺序（本例按注册顺序算运气好）。有顺序依赖就别用观察者，那是 ⑰ 责任链的地盘。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>通知风暴\u003C\u002Fstrong>。A 通知 B，B 状态变化又通知 A……链式反应没设计好就是死循环，CPU 直接拉满。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>内存泄漏风险\u003C\u002Fstrong>。Subject 持有 Observer 的强引用，detach 忘了调，观察者永远无法被 GC——Swing 程序内存泄漏的老病灶。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>一个观察者抛异常，广播中断\u003C\u002Fstrong>。本例循环里没有 try-catch，短信挂了库存就收不到通知。生产代码必须给每个观察者的调用套上异常隔离。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-3\">三、什么时候用\u003C\u002Fh2>\n\u003Cp>判断标准：\u003Cstrong>一件事发生后，有多个互不相识的下游关心，且这份名单会变。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>业务事件广播\u003C\u002Fstrong>：支付成功、注册成功、审批通过 → 短信\u002F积分\u002F风控\u002F埋点\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对象状态联动\u003C\u002Fstrong>：表格数据变了，柱状图、折线图、导出模块都要刷新（MVC 里 View 监听 Model）\u003C\u002Fli>\n \u003Cli>\u003Cstrong>跨模块解耦\u003C\u002Fstrong>：订单模块不想认识物流模块，但物流想感知订单\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>反面对照：下游固定就一个、或者下游之间有先后顺序、或者需要拿到处理结果——都不适合观察者，那是策略（⑮）、责任链（⑰）或者直接调用的场景。\u003C\u002Fp>\n\u003Ch2 id=\"toc-4\">四、实现方式：把\"点对点调用\"改成\"广播订阅\"\u003C\u002Fh2>\n\u003Ch3 id=\"toc-5\">4.1 反面教材：下单后依次调三个下游\u003C\u002Fh3>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> paySuccess\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[订单] 订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 支付成功\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [短信] 尊敬的用户，订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 已支付\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [积分] 订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 增加 100 积分\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [库存] 扣减订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 对应库存\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 下个月要接物流、接风控、接数据埋点……\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">        \u002F\u002F 每接一个下游，这个方法就得改一次，回归测试全量重跑\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>OrderService 认识了三个下游：发短信的、加积分的、扣库存的。每接一个新下游，这个方法就得改一次——和 ⑮ 的 if-else 巨无霸是同一种病：\u003Cstrong>把\"变化的部分\"焊死在\"稳定的部分\"里\u003C\u002Fstrong>。区别只是策略模式里变化的是算法分支，这里变化的是下游名单。\u003C\u002Fp>\n\u003Ch3 id=\"toc-6\">4.2 正解：Subject 拿名单，Observer 各干各的\u003C\u002Fh3>\n\u003Cp>第一步，观察者接口——\"收到订单支付通知后干什么\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> interface\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> onOrderPaid\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第二步，被观察者——维护名单 + 业务方法里广播：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderSubject\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> List&#x3C;\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">OrderObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">> observers \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> ArrayList&#x3C;>();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 订阅：观察者注册进名单\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> attach\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(OrderObserver \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">observer\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        observers.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">add\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(observer);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">    \u002F\u002F 退订：从名单里移除，之后的广播不再收到\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> detach\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(OrderObserver \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">observer\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        observers.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">remove\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(observer);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> paySuccess\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"[订单] 订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 支付成功，开始广播\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (OrderObserver observer \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> observers) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            observer.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">onOrderPaid\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(orderId);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>第三步，三个具体观察者：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> SmsObserver\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> onOrderPaid\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [短信] 尊敬的用户，订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 已支付\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PointsObserver\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> onOrderPaid\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [积分] 订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 增加 100 积分\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> StockObserver\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> implements\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> onOrderPaid\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">orderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"  [库存] 扣减订单 \"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> orderId \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \" 对应库存\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试类跑一遍——重点看后半段，物流中途接入、短信中途退订：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> ObserverTest\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> main\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">String\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">[] \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">args\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 反面教材：下单后直接调三个下游 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> BadOrderService\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">paySuccess\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"1001\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"========== 正解：观察者模式 ==========\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        OrderSubject subject \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderSubject\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        SmsObserver sms \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> SmsObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        subject.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">attach\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(sms);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        subject.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">attach\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PointsObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        subject.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">attach\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> StockObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        subject.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">paySuccess\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"2001\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"-- 新下游「物流」接入：不改 OrderSubject，注册即生效 --\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        subject.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">attach\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> LogisticsObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        subject.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">paySuccess\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"3001\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"-- 短信观察者退订（detach），广播立刻少一家 --\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        subject.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">detach\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(sms);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        subject.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">paySuccess\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"4001\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行输出：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>========== 反面教材：下单后直接调三个下游 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[订单] 订单 1001 支付成功\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [短信] 尊敬的用户，订单 1001 已支付\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [积分] 订单 1001 增加 100 积分\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [库存] 扣减订单 1001 对应库存\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>========== 正解：观察者模式 ==========\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[订单] 订单 2001 支付成功，开始广播\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [短信] 尊敬的用户，订单 2001 已支付\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [积分] 订单 2001 增加 100 积分\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [库存] 扣减订单 2001 对应库存\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>-- 新下游「物流」接入：不改 OrderSubject，注册即生效 --\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[订单] 订单 3001 支付成功，开始广播\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [短信] 尊敬的用户，订单 3001 已支付\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [积分] 订单 3001 增加 100 积分\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [库存] 扣减订单 3001 对应库存\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [物流] 通知仓库拣货发货：订单 3001\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>-- 短信观察者退订（detach），广播立刻少一家 --\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>[订单] 订单 4001 支付成功，开始广播\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [积分] 订单 4001 增加 100 积分\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [库存] 扣减订单 4001 对应库存\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>  [物流] 通知仓库拣货发货：订单 4001\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"toc-7\">4.3 三处细节咂摸一下\u003C\u002Fh3>\n\u003Cp>**细节一：订单 3001 多了一行物流，OrderSubject 没改一行代码。**写 LogisticsObserver、attach，广播自然多一家。开闭原则的达成方式和 ⑮ 一模一样：新变化 = 新类，不是改旧类。\u003C\u002Fp>\n\u003Cp>**细节二：订单 4001 少了短信，也是运行时动态生效。**观察者名单是活的，订阅退订都是运行期行为。这是\"依次调用三个方法\"永远做不到的。\u003C\u002Fp>\n\u003Cp>**细节三：广播循环只有一行 \u003Ccode>observer.onOrderPaid(orderId)\u003C\u002Fcode>。**Subject 对观察者的一切认知浓缩在这行里：它是个 OrderObserver，出了事我调它。至于它是谁、要干什么、干砸了怎么办——Subject 一概不知，也不想知道。这行代码就是\"依赖倒置\"的最小标本：依赖抽象，不依赖具体。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">4.4 生产环境要补的三块拼图\u003C\u002Fh3>\n\u003Cp>教程版到此为止，生产代码还要补三件事：\u003C\u002Fp>\n\u003Cp>\u003Cstrong>① 异常隔离\u003C\u002Fstrong>——一个观察者炸了不能连累其他观察者：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">for\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (OrderObserver observer \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">:\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> observers) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    try\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        observer.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">onOrderPaid\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(orderId);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    } \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">catch\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> (Exception \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">e\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        log.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">error\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"观察者执行失败: {}\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, observer.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getClass\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">().\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getSimpleName\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(), e);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>② 并发安全\u003C\u002Fstrong>——如果广播的同时有别的线程 attach\u002Fdetach，ArrayList 会抛 ConcurrentModificationException。监听器列表的经典选择是 \u003Ccode>CopyOnWriteArrayList\u003C\u002Fcode>（读多写少场景正合适），Swing 里则用专门的 \u003Ccode>EventListenerList\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>③ 生命周期管理\u003C\u002Fstrong>——长生命周期 Subject 挂着短生命周期观察者，用完必须 detach，否则就是第二节说的内存泄漏。Java 里也可以用弱引用（\u003Ccode>WeakReference\u003C\u002Fcode>）兜底，观察者没别的强引用时自动从名单里\"失效\"。\u003C\u002Fp>\n\u003Ch2 id=\"toc-9\">五、在框架里找它\u003C\u002Fh2>\n\u003Cp>**JDK 的 Observable——一个反面教材式的官方实现。**java.util.Observable 从 JDK 1.0 就有（Observable 类 + Observer 接口），但 JDK 9 起被标记 @Deprecated。为什么？它是类不是接口，想观察就得继承它，把唯一的继承名额烧了（这个理由 ⑭ 里也吐槽过）；setChanged\u002FnotifyObservers 的状态耦合设计也别扭。\u003Cstrong>官方亲手示范了\"观察者思想的正确性\"和\"API 设计的失败\"可以并存\u003C\u002Fstrong>——思想用你的，代码自己写。\u003C\u002Fp>\n\u003Cp>**Swing\u002FAWT 事件监听。**每个按钮都是被观察者，每个 ActionListener 都是观察者：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">button.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">addActionListener\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(e \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">->\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"被点了\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>awt 的事件体系把观察者模式铺到了桌面编程的每个角落。\u003C\u002Fp>\n\u003Cp>**Spring 的事件机制——Java 后端最常用形态。**发布方注入 ApplicationEventPublisher，一行发布；接收方一个注解订阅，连 attach 都省了：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 发布：订单服务里\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">publisher.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">publishEvent\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> OrderPaidEvent\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(orderId));\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 订阅：物流模块里\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">EventListener\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> onOrderPaid\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(OrderPaidEvent event) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    logisticsService.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">arrangeShipment\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(event.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getOrderId\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>默认同步执行（发布线程里跑监听器），想异步加 \u003Ccode>@Async\u003C\u002Fcode>；想保证事务提交后才通知，用 \u003Ccode>@TransactionalEventListener\u003C\u002Fcode>——同步\u002F异步\u002F事务边界三个开关，把教程版观察者的坑全填了。\u003C\u002Fp>\n\u003Cp>**Guava EventBus。**把\"接口约束 + 名单维护\"简化成\"一个注解 + 一个 register\"：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">eventBus.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">register\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> LogisticsObserver\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());  \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 无需实现任何接口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">@\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Subscribe\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> onOrderPaid\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(OrderPaidEvent event) { ... }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>按事件类型自动分发，Spring 的 @EventListener 明显师承于此。\u003C\u002Fp>\n\u003Cp>**消息队列——观察者的分布式形态。**RocketMQ\u002FKafka 的发布订阅，把\"Subject 持有 Observer 引用\"换成\"大家都只连 Broker\"：生产者发消息，N 个消费组各自消费。观察者模式的解耦思想放大到跨进程，就成了 MQ。第六节接着这个话头展开。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">六、观察者 vs 发布订阅——面试高频题\u003C\u002Fh2>\n\u003Cp>很多文章把两个词混着用，面试官却爱较真。区别一句话：\u003Cstrong>看有没有中间人。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>观察者模式\u003C\u002Fth>\n   \u003Cth>发布订阅模式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>结构\u003C\u002Ftd>\n   \u003Ctd>Subject **直接持有** Observer 引用\u003C\u002Ftd>\n   \u003Ctd>发布者和订阅者**互不相识**，中间隔着事件总线\u002FBroker\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>耦合\u003C\u002Ftd>\n   \u003Ctd>松耦合，但同在一个进程里\u003C\u002Ftd>\n   \u003Ctd>完全解耦，可跨进程、跨机器\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>时机\u003C\u002Ftd>\n   \u003Ctd>通常同步，通知完才返回\u003C\u002Ftd>\n   \u003Ctd>通常异步，发完即走\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型\u003C\u002Ftd>\n   \u003Ctd>本篇 OrderSubject、Swing\u003C\u002Ftd>\n   \u003Ctd>MQ、Redis pub\u002Fsub、Spring 事件（半个）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>本篇手写的 GoF 版是纯观察者；Spring 事件因为引入了 ApplicationEventPublisher 这个\"隐形中间人\"，已经带一点发布订阅的意思；到了 MQ 就是彻底的发布订阅。\u003Cstrong>观察者是设计模式，发布订阅是架构风格\u003C\u002Fstrong>——这么答，面试官一般会点头。\u003C\u002Fp>\n\u003Ch2 id=\"toc-11\">写在最后\u003C\u002Fh2>\n\u003Cp>观察者模式一句话总结：\u003Cstrong>一方有事，名单广播；订阅自愿，退订自由。\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>它和策略模式合起来覆盖了行为型的两大解耦方向：⑮ 解耦\"用哪个算法\"（一对一），⑯ 解耦\"通知哪些下游\"（一对多）。下一篇 ⑰ 责任链模式补上第三种：\u003Cstrong>请求交给谁处理\u003C\u002Fstrong>——报销单从你手里递出去，组长批不过找经理，经理批不过找总监，请求沿着链条一站一站传下去，谁有权限谁处理。审批流的经典实现，下一篇见。\u003C\u002Fp>",[112,115,118,121,124,128,131,134,137,140,143],{"id":113,"text":114,"level":107},"toc-1","一、观察者模式是什么",{"id":116,"text":117,"level":107},"toc-2","二、优点与缺点",{"id":119,"text":120,"level":107},"toc-3","三、什么时候用",{"id":122,"text":123,"level":107},"toc-4","四、实现方式：把\"点对点调用\"改成\"广播订阅\"",{"id":125,"text":126,"level":127},"toc-5","4.1 反面教材：下单后依次调三个下游",3,{"id":129,"text":130,"level":127},"toc-6","4.2 正解：Subject 拿名单，Observer 各干各的",{"id":132,"text":133,"level":127},"toc-7","4.3 三处细节咂摸一下",{"id":135,"text":136,"level":127},"toc-8","4.4 生产环境要补的三块拼图",{"id":138,"text":139,"level":107},"toc-9","五、在框架里找它",{"id":141,"text":142,"level":107},"toc-10","六、观察者 vs 发布订阅——面试高频题",{"id":144,"text":145,"level":107},"toc-11","写在最后"]