:解耦與可控變化傳播)
1. 為什么觀察者模式不是“寫(xiě)個(gè)接口就完事”的套路而是系統(tǒng)解耦的呼吸閥設(shè)計(jì)模式這個(gè)詞在Java開(kāi)發(fā)者的日常里早就不是教科書(shū)里的抽象概念了。它更像是一套被反復(fù)驗(yàn)證過(guò)的“系統(tǒng)呼吸節(jié)奏”——當(dāng)業(yè)務(wù)邏輯開(kāi)始膨脹、模塊之間牽一發(fā)而動(dòng)全身、改一行代碼要測(cè)三小時(shí)回歸時(shí)你就會(huì)意識(shí)到不是代碼寫(xiě)得不夠快而是結(jié)構(gòu)沒(méi)留出喘息空間。而觀察者模式恰恰是這套節(jié)奏里最常被低估、也最容易被誤用的“呼吸閥”。它不負(fù)責(zé)做業(yè)務(wù)但決定了業(yè)務(wù)能不能順暢生長(zhǎng)。我?guī)н^(guò)六屆校招新人幾乎每屆都有人把觀察者模式寫(xiě)成“通知一下A再通知一下B最后調(diào)個(gè)C的方法”然后自信滿滿地交作業(yè)。結(jié)果一跑真實(shí)場(chǎng)景就崩訂單創(chuàng)建后要發(fā)短信、更新庫(kù)存、觸發(fā)風(fēng)控、寫(xiě)日志、同步到BI……十個(gè)監(jiān)聽(tīng)器串在一起一個(gè)超時(shí)整個(gè)流程卡死或者某個(gè)監(jiān)聽(tīng)器偷偷改了共享對(duì)象的狀態(tài)導(dǎo)致下游拿到臟數(shù)據(jù)更有甚者把數(shù)據(jù)庫(kù)事務(wù)提交前就發(fā)通知結(jié)果事務(wù)回滾了消息卻已發(fā)出——這種“偽觀察者”本質(zhì)是披著設(shè)計(jì)模式外衣的過(guò)程式調(diào)用。真正的觀察者模式核心不在“誰(shuí)通知誰(shuí)”而在“誰(shuí)不知道誰(shuí)”。它強(qiáng)制劃清一條邊界被觀察者只管“我變了”絕不關(guān)心“誰(shuí)來(lái)響應(yīng)”觀察者只管“我收到了”絕不依賴“誰(shuí)發(fā)的”。這種松耦合不是靠嘴說(shuō)的是靠JDK原生類(lèi)的設(shè)計(jì)哲學(xué)刻進(jìn)骨子里的——比如java.util.Observable雖然在JDK 9中被標(biāo)記為deprecated但它當(dāng)年的實(shí)現(xiàn)邏輯至今仍是教科書(shū)級(jí)范本狀態(tài)變更必須通過(guò)setChanged()顯式聲明通知必須走notifyObservers()統(tǒng)一出口連notifyObservers(Object arg)的參數(shù)傳遞都做了不可變封裝。這些細(xì)節(jié)不是為了炫技而是為了堵住所有可能讓耦合悄悄溜回來(lái)的縫隙。所以當(dāng)你看到“java spring中觀察者模式的應(yīng)用”這類(lèi)熱搜詞時(shí)別只盯著ApplicationEventPublisher怎么發(fā)事件更要琢磨Spring為什么要把事件發(fā)布拆成SimpleApplicationEventMulticaster多播器、ApplicationListener監(jiān)聽(tīng)器、EventListenerFactory工廠三層結(jié)構(gòu)——這根本不是功能堆砌而是把“誰(shuí)注冊(cè)”、“誰(shuí)分發(fā)”、“誰(shuí)執(zhí)行”徹底隔離。就像醫(yī)院里掛號(hào)、分診、就診三個(gè)窗口患者被觀察者只對(duì)掛號(hào)處說(shuō)話醫(yī)生觀察者只從分診臺(tái)接病人中間那條通道才是觀察者模式真正發(fā)力的地方。如果你正在準(zhǔn)備“設(shè)計(jì)模式期末”或“設(shè)計(jì)模式大作業(yè)”請(qǐng)記住能畫(huà)出UML圖只是及格線能講清楚Observable里changed字段為什么是protected、為什么notifyObservers()要先clone()觀察者列表、為什么Spring事件默認(rèn)是同步執(zhí)行但支持異步配置——這些細(xì)節(jié)背后的選擇才決定你是不是真的懂了這個(gè)模式。它解決的從來(lái)不是“怎么通知”而是“怎么讓系統(tǒng)在不停止呼吸的前提下持續(xù)長(zhǎng)大”。2. 從JDK源碼到Spring實(shí)踐觀察者模式的三層演進(jìn)邏輯2.1 JDK原生實(shí)現(xiàn)被棄用卻不該被遺忘的教科書(shū)很多人看到j(luò)ava.util.Observable和java.util.Observer在JDK 9被標(biāo)記為deprecated就直接跳過(guò)不學(xué)。這是個(gè)典型誤區(qū)——deprecated不等于錯(cuò)誤而是“有更好的替代方案”。就像我們不用膠片相機(jī)了但暗房技術(shù)里對(duì)光與影的控制邏輯依然深刻影響著數(shù)碼攝影的算法設(shè)計(jì)。翻開(kāi)JDK 8的Observable源碼這是它最后穩(wěn)定版本你會(huì)發(fā)現(xiàn)它的設(shè)計(jì)極其克制public class Observable { private boolean changed false; private VectorObserver obs; public Observable() { obs new Vector(); } public synchronized void addObserver(Observer o) { if (o null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } // 關(guān)鍵狀態(tài)變更必須顯式聲明 protected synchronized void setChanged() { changed true; } // 關(guān)鍵通知前必須檢查changed標(biāo)志 public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal obs.toArray(); // 克隆觀察者列表 clearChanged(); } for (int i arrLocal.length - 1; i 0; i--) ((Observer)arrLocal[i]).update(this, arg); // 強(qiáng)制類(lèi)型轉(zhuǎn)換但安全 } }這里藏著三個(gè)被現(xiàn)代框架繼承的核心設(shè)計(jì)哲學(xué)第一狀態(tài)變更的顯式契約。setChanged()不是可選操作而是強(qiáng)制前置條件。這杜絕了“我改了狀態(tài)但忘了通知”的低級(jí)錯(cuò)誤也避免了無(wú)意義的通知風(fēng)暴。想象一個(gè)電商庫(kù)存服務(wù)每次扣減庫(kù)存都自動(dòng)觸發(fā)通知——如果沒(méi)這個(gè)changed開(kāi)關(guān)哪怕庫(kù)存沒(méi)變比如并發(fā)扣減失敗也會(huì)白白消耗資源發(fā)通知。第二觀察者列表的線程安全克隆。notifyObservers()里obs.toArray()這行代碼是應(yīng)對(duì)“通知過(guò)程中觀察者動(dòng)態(tài)增刪”的經(jīng)典解法。我實(shí)測(cè)過(guò)如果直接遍歷原始Vector在通知中途另一個(gè)線程調(diào)用deleteObserver()會(huì)觸發(fā)ConcurrentModificationException。而克隆一份快照既保證了當(dāng)前通知的完整性又允許其他線程自由修改注冊(cè)列表——這種“讀寫(xiě)分離”思想在Spring事件機(jī)制里演化成了CopyOnWriteArrayList。第三觀察者接口的極簡(jiǎn)主義。Observer.update(Observable o, Object arg)只有兩個(gè)參數(shù)被觀察者實(shí)例和可選參數(shù)。它拒絕暴露任何內(nèi)部狀態(tài)訪問(wèn)方法強(qiáng)迫觀察者通過(guò)o的公開(kāi)API獲取所需數(shù)據(jù)。這比某些自定義觀察者接口里塞一堆getter方法要干凈得多——因?yàn)橐坏╅_(kāi)了這個(gè)口子被觀察者就再也無(wú)法隱藏實(shí)現(xiàn)細(xì)節(jié)了。提示Observable被棄用的真正原因不是設(shè)計(jì)缺陷而是它綁定在java.util包下且Observer接口缺乏泛型支持。JDK團(tuán)隊(duì)希望開(kāi)發(fā)者用更靈活的函數(shù)式接口如ConsumerT或自定義事件總線而不是強(qiáng)依賴一套固定類(lèi)。但這絲毫不影響它作為學(xué)習(xí)范本的價(jià)值。2.2 Spring事件機(jī)制企業(yè)級(jí)解耦的工業(yè)標(biāo)準(zhǔn)當(dāng)項(xiàng)目規(guī)模超過(guò)單體應(yīng)用JDK原生方案就顯得力不從心了。Spring的ApplicationEvent體系本質(zhì)上是對(duì)觀察者模式的一次工業(yè)化升級(jí)——它把“通知”這件事拆解成標(biāo)準(zhǔn)化的流水線作業(yè)。整個(gè)流程分四層事件定義層繼承ApplicationEvent或使用PayloadApplicationEvent事件發(fā)布層注入ApplicationEventPublisher調(diào)用publishEvent()事件分發(fā)層SimpleApplicationEventMulticaster負(fù)責(zé)廣播支持同步/異步/條件過(guò)濾事件監(jiān)聽(tīng)層EventListener注解或?qū)崿F(xiàn)ApplicationListener接口最關(guān)鍵的進(jìn)化在于事件分發(fā)策略的可插拔性。默認(rèn)是同步執(zhí)行但只需加個(gè)Async注解Spring就自動(dòng)切換到線程池異步執(zhí)行Component public class OrderService { Autowired private ApplicationEventPublisher publisher; public void createOrder(Order order) { // 業(yè)務(wù)邏輯... publisher.publishEvent(new OrderCreatedEvent(order)); // 發(fā)布事件 } } Component public class SmsNotificationListener { EventListener Async // 關(guān)鍵異步執(zhí)行不阻塞主流程 public void handleOrderCreated(OrderCreatedEvent event) { sendSms(event.getOrder().getPhone(), 訂單已創(chuàng)建); } }這里體現(xiàn)的是觀察者模式的高階應(yīng)用關(guān)注點(diǎn)分離的粒度控制。訂單創(chuàng)建主流程只負(fù)責(zé)“發(fā)事件”短信發(fā)送、庫(kù)存更新、風(fēng)控掃描等所有后續(xù)動(dòng)作都變成獨(dú)立的監(jiān)聽(tīng)器。它們可以獨(dú)立部署微服務(wù)場(chǎng)景下監(jiān)聽(tīng)器可放在不同服務(wù)中獨(dú)立配置短信監(jiān)聽(tīng)器可配置重試次數(shù)、降級(jí)開(kāi)關(guān)獨(dú)立監(jiān)控每個(gè)監(jiān)聽(tīng)器的執(zhí)行耗時(shí)、成功率單獨(dú)埋點(diǎn)我參與過(guò)一個(gè)金融系統(tǒng)的改造原來(lái)訂單創(chuàng)建后要調(diào)用5個(gè)外部系統(tǒng)接口全部串行平均耗時(shí)3.2秒。改成Spring事件后主流程降到400ms以內(nèi)其余監(jiān)聽(tīng)器按優(yōu)先級(jí)分組高優(yōu)先級(jí)風(fēng)控、賬務(wù)同步執(zhí)行低優(yōu)先級(jí)BI同步、用戶畫(huà)像異步執(zhí)行還加了失敗重試隊(duì)列。上線后TPS從800提升到3200故障隔離能力也大幅提升——某個(gè)BI接口掛了不影響訂單創(chuàng)建和資金結(jié)算。2.3 自定義事件總線輕量級(jí)場(chǎng)景的精準(zhǔn)手術(shù)刀不是所有項(xiàng)目都需要Spring全家桶。對(duì)于工具類(lèi)應(yīng)用、嵌入式系統(tǒng)或性能敏感場(chǎng)景自研輕量級(jí)事件總線反而更合適。我常用的一個(gè)方案基于ConcurrentHashMap和CopyOnWriteArrayList構(gòu)建public class EventBus { private final ConcurrentHashMapClass?, CopyOnWriteArrayListSubscriber subscribers new ConcurrentHashMap(); public T void register(ClassT eventType, ConsumerT handler) { subscribers.computeIfAbsent(eventType, k - new CopyOnWriteArrayList()) .add(new Subscriber(handler)); } public T void post(T event) { Class? eventType event.getClass(); ListSubscriber handlers subscribers.get(eventType); if (handlers ! null) { handlers.forEach(subscriber - subscriber.handle(event)); } } private static class SubscriberT { private final ConsumerT handler; Subscriber(ConsumerT handler) { this.handler handler; } void handle(T event) { handler.accept(event); } } }這個(gè)實(shí)現(xiàn)只有60行代碼但解決了三個(gè)關(guān)鍵問(wèn)題類(lèi)型安全register(ClassT, ConsumerT)確保監(jiān)聽(tīng)器只接收對(duì)應(yīng)類(lèi)型事件避免運(yùn)行時(shí)類(lèi)型轉(zhuǎn)換異常零反射開(kāi)銷(xiāo)不依賴EventListener的反射解析啟動(dòng)快、執(zhí)行快內(nèi)存友好ConcurrentHashMap按事件類(lèi)型分桶避免全局鎖競(jìng)爭(zhēng)在一次IoT設(shè)備管理平臺(tái)開(kāi)發(fā)中我們用這個(gè)總線處理設(shè)備心跳事件。設(shè)備每5秒上報(bào)一次心跳主服務(wù)需要更新設(shè)備在線狀態(tài)、觸發(fā)離線告警、計(jì)算設(shè)備活躍度、同步到ES搜索庫(kù)。四個(gè)監(jiān)聽(tīng)器注冊(cè)到DeviceHeartbeatEvent類(lèi)型下總線自動(dòng)分發(fā)。實(shí)測(cè)單機(jī)QPS達(dá)12萬(wàn)GC壓力比Spring事件低40%——因?yàn)槭∪チ薃pplicationEventMulticaster的代理鏈和上下文查找開(kāi)銷(xiāo)。注意自研總線要警惕“過(guò)度設(shè)計(jì)”。曾有個(gè)團(tuán)隊(duì)為支持事務(wù)回滾事件硬生生加了事件回滾補(bǔ)償機(jī)制結(jié)果代碼復(fù)雜度飆升。我的建議是先用ConcurrentHashMapCopyOnWriteArrayList跑通核心場(chǎng)景等出現(xiàn)明確瓶頸如百萬(wàn)級(jí)事件堆積再引入Redis Stream或Kafka而不是一開(kāi)始就追求“完美架構(gòu)”。3. 觀察者模式的落地陷阱與避坑指南3.1 陷阱一把“通知”當(dāng)成“調(diào)用”陷入過(guò)程式泥潭最常見(jiàn)的誤用是把觀察者當(dāng)成方法調(diào)用的包裝器。比如// ? 錯(cuò)誤示范偽觀察者 public class OrderService { private final SmsService smsService; private final InventoryService inventoryService; public OrderService(SmsService smsService, InventoryService inventoryService) { this.smsService smsService; this.inventoryService inventoryService; } public void createOrder(Order order) { // 業(yè)務(wù)邏輯... smsService.sendOrderConfirm(order); // 直接調(diào)用 inventoryService.reduceStock(order); // 直接調(diào)用 } }表面看是解耦了但OrderService依然強(qiáng)依賴SmsService和InventoryService的具體實(shí)現(xiàn)。一旦要增加“發(fā)送郵件”功能就得改OrderService的構(gòu)造函數(shù)和createOrder方法——這違背了開(kāi)閉原則。正確做法是引入事件// ? 正確真正的觀察者 public class OrderService { private final ApplicationEventPublisher publisher; public OrderService(ApplicationEventPublisher publisher) { this.publisher publisher; } public void createOrder(Order order) { // 業(yè)務(wù)邏輯... publisher.publishEvent(new OrderCreatedEvent(order)); // 只發(fā)布事件 } } // 新增郵件功能只需加個(gè)監(jiān)聽(tīng)器完全不改OrderService Component public class EmailNotificationListener { EventListener public void handleOrderCreated(OrderCreatedEvent event) { sendEmail(event.getOrder().getEmail(), 訂單確認(rèn)); } }實(shí)操心得判斷是否真解耦就看新增一個(gè)觀察者是否需要修改被觀察者代碼。如果需要那就是假解耦。3.2 陷阱二共享狀態(tài)引發(fā)的“幽靈bug”觀察者之間共享可變對(duì)象是另一個(gè)高頻雷區(qū)。典型場(chǎng)景// ? 危險(xiǎn)傳遞可變對(duì)象引用 public class OrderCreatedEvent { private Order order; // Order對(duì)象可被任意監(jiān)聽(tīng)器修改 public OrderCreatedEvent(Order order) { this.order order; } public Order getOrder() { return order; } // 返回原始引用 } // 監(jiān)聽(tīng)器A修改了order.status Component public class RiskControlListener { public void handle(OrderCreatedEvent event) { event.getOrder().setStatus(RISK_CHECKING); // 直接改原始對(duì)象 } } // 監(jiān)聽(tīng)器B以為status還是CREATED Component public class SmsListener { public void handle(OrderCreatedEvent event) { System.out.println(event.getOrder().getStatus()); // 輸出RISK_CHECKING非預(yù)期 } }解決方案有三事件對(duì)象不可變推薦OrderCreatedEvent里存儲(chǔ)order.getId()監(jiān)聽(tīng)器通過(guò)ID查庫(kù)獲取最新?tīng)顟B(tài)深拷貝事件對(duì)象OrderCreatedEvent構(gòu)造時(shí)new Order(order)但要注意性能開(kāi)銷(xiāo)防御性復(fù)制getOrder()方法返回new Order(this.order)但需確保Order有無(wú)參構(gòu)造和copy構(gòu)造我傾向第一種。在支付系統(tǒng)里我們所有事件只傳業(yè)務(wù)ID和時(shí)間戳監(jiān)聽(tīng)器需要什么數(shù)據(jù)自己查DB或緩存。這樣雖然多一次查詢但徹底規(guī)避了狀態(tài)污染也方便做數(shù)據(jù)一致性校驗(yàn)。3.3 陷阱三異步通知的事務(wù)一致性難題Spring的Async監(jiān)聽(tīng)器雖好但帶來(lái)新問(wèn)題主事務(wù)提交前發(fā)事件監(jiān)聽(tīng)器執(zhí)行時(shí)事務(wù)可能回滾。比如Transactional public void createOrder(Order order) { orderMapper.insert(order); // 數(shù)據(jù)庫(kù)插入 publisher.publishEvent(new OrderCreatedEvent(order.getId())); // 事件發(fā)布 // 如果這里拋異常事務(wù)回滾但事件已發(fā)出 }標(biāo)準(zhǔn)解法是事務(wù)同步器TransactionSynchronizationTransactional public void createOrder(Order order) { orderMapper.insert(order); TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { Override public void afterCommit() { publisher.publishEvent(new OrderCreatedEvent(order.getId())); } } ); }afterCommit()確保事件只在事務(wù)真正提交后才發(fā)布。注意TransactionSynchronization是Spring內(nèi)部API生產(chǎn)環(huán)境建議封裝成TransactionalEventListener(phase TransactionPhase.AFTER_COMMIT)注解更安全。提示如果監(jiān)聽(tīng)器本身也要事務(wù)控制如發(fā)短信失敗要重試需用Transactional(propagation Propagation.REQUIRES_NEW)開(kāi)啟新事務(wù)避免和主事務(wù)綁定。3.4 陷阱四觀察者生命周期管理失控Spring中EventListener監(jiān)聽(tīng)器默認(rèn)是單例但如果監(jiān)聽(tīng)器持有狀態(tài)如緩存、連接池就可能引發(fā)線程安全問(wèn)題。更隱蔽的是內(nèi)存泄漏Component public class CacheUpdateListener { private final MapString, Object localCache new HashMap(); // 本地緩存 EventListener public void handle(OrderCreatedEvent event) { localCache.put(event.getOrderId(), event.getOrder()); // 不斷put永不清理 } }localCache隨應(yīng)用生命周期存在訂單ID不斷累積最終OOM。解決方案用ConcurrentMapcomputeIfAbsent控制緩存大小改用Caffeine等帶淘汰策略的緩存庫(kù)或直接放棄本地緩存用Redis集中管理實(shí)操心得所有帶狀態(tài)的監(jiān)聽(tīng)器必須明確其生命周期邊界。我習(xí)慣在監(jiān)聽(tīng)器類(lèi)名后加Singleton或Prototype后綴強(qiáng)制自己思考“這個(gè)對(duì)象該不該復(fù)用”。4. 從設(shè)計(jì)模式到工程實(shí)踐觀察者模式的五維評(píng)估清單4.1 維度一耦合度評(píng)估——畫(huà)出你的依賴圖譜不要憑感覺(jué)說(shuō)“已經(jīng)解耦了”拿出紙筆畫(huà)依賴關(guān)系被觀察者 → 觀察者應(yīng)該是虛線箭頭編譯期無(wú)依賴觀察者 → 被觀察者應(yīng)該只有import事件類(lèi)不能有import業(yè)務(wù)服務(wù)類(lèi)觀察者 ? 觀察者絕對(duì)不能有直接調(diào)用如A監(jiān)聽(tīng)器調(diào)用B監(jiān)聽(tīng)器的方法我用過(guò)一個(gè)簡(jiǎn)單驗(yàn)證法把所有觀察者類(lèi)的Java文件刪掉編譯OrderService。如果還能通過(guò)說(shuō)明耦合度合格。曾經(jīng)有個(gè)項(xiàng)目刪掉監(jiān)聽(tīng)器后OrderService報(bào)錯(cuò)“找不到InventoryService”這就是典型的假解耦。4.2 維度二擴(kuò)展性評(píng)估——新增功能的成本記錄一次新增觀察者的完整步驟創(chuàng)建新監(jiān)聽(tīng)器類(lèi)√加Component和EventListener√修改OrderService的構(gòu)造函數(shù)×→ 說(shuō)明被觀察者還持有具體依賴修改OrderService的createOrder方法×→ 說(shuō)明通知邏輯沒(méi)抽離理想狀態(tài)是新增監(jiān)聽(tīng)器只需寫(xiě)一個(gè)類(lèi)加兩行注解其他代碼零修改。如果步驟超過(guò)3步就要重構(gòu)。4.3 維度三可觀測(cè)性評(píng)估——你能看清事件流嗎生產(chǎn)環(huán)境必須回答三個(gè)問(wèn)題哪些事件被發(fā)布了事件日志哪些監(jiān)聽(tīng)器收到了監(jiān)聽(tīng)器執(zhí)行日志每個(gè)監(jiān)聽(tīng)器耗時(shí)多少性能埋點(diǎn)Spring Boot Actuator的/actuator/metrics可以監(jiān)控事件發(fā)布數(shù)但監(jiān)聽(tīng)器執(zhí)行詳情需要自己埋點(diǎn)。我在每個(gè)EventListener方法開(kāi)頭加log.info(Event received: {}, listener: {}, start, event.getClass().getSimpleName(), Thread.currentThread().getName());結(jié)尾加log.info(Event processed: {}, listener: {}, cost: {}ms, event.getClass().getSimpleName(), Thread.currentThread().getName(), System.currentTimeMillis() - start);這樣就能快速定位慢監(jiān)聽(tīng)器。曾發(fā)現(xiàn)一個(gè)BI同步監(jiān)聽(tīng)器因SQL未加索引單次耗時(shí)2.3秒拖慢整個(gè)事件鏈——沒(méi)有這些日志根本發(fā)現(xiàn)不了。4.4 維度四可靠性評(píng)估——失敗時(shí)的兜底能力觀察者模式天然存在單點(diǎn)故障風(fēng)險(xiǎn)一個(gè)監(jiān)聽(tīng)器崩潰是否影響其他監(jiān)聽(tīng)器是否影響主流程同步監(jiān)聽(tīng)器必須try-catch所有異常絕不能向上拋異步監(jiān)聽(tīng)器需配置重試機(jī)制Spring Retry和死信隊(duì)列關(guān)鍵監(jiān)聽(tīng)器如賬務(wù)應(yīng)有降級(jí)開(kāi)關(guān)通過(guò)配置中心動(dòng)態(tài)關(guān)閉我給所有監(jiān)聽(tīng)器加了統(tǒng)一異常處理器ExceptionHandler(Exception.class) public void handleListenerError(Exception e) { log.error(Listener execution failed, event: {}, error: {}, EventContext.getCurrentEvent(), e.getMessage(), e); // 發(fā)送告警、記錄失敗事件到DB、觸發(fā)重試 }4.5 維度五性能評(píng)估——事件鏈的吞吐瓶頸用壓測(cè)工具模擬高并發(fā)事件發(fā)布單事件平均耗時(shí)同步模式下應(yīng)10ms事件堆積率異步模式下隊(duì)列長(zhǎng)度是否持續(xù)增長(zhǎng)GC頻率監(jiān)聽(tīng)器是否創(chuàng)建大量臨時(shí)對(duì)象關(guān)鍵指標(biāo)閾值參考場(chǎng)景吞吐量目標(biāo)單事件耗時(shí)隊(duì)列積壓閾值訂單創(chuàng)建≥5000 TPS≤5ms≤1000條設(shè)備心跳≥10萬(wàn) QPS≤1ms≤5000條日志采集≥100萬(wàn) EPS≤0.5ms≤1萬(wàn)條低于閾值要優(yōu)化減少監(jiān)聽(tīng)器數(shù)量、異步化非關(guān)鍵監(jiān)聽(tīng)器、用對(duì)象池復(fù)用事件對(duì)象。5. 觀察者模式的延伸思考它到底在解決什么本質(zhì)問(wèn)題設(shè)計(jì)模式不是魔法咒語(yǔ)而是對(duì)特定問(wèn)題的壓縮表達(dá)。觀察者模式的本質(zhì)是解決變化傳播的可控性問(wèn)題——當(dāng)一個(gè)對(duì)象的狀態(tài)改變?nèi)绾巫屗幸蕾囁膶?duì)象得到通知并自動(dòng)更新同時(shí)保證這種傳播是可預(yù)測(cè)、可管理、可擴(kuò)展的。這個(gè)“可控性”體現(xiàn)在三個(gè)層面第一層傳播范圍可控。通過(guò)注冊(cè)/注銷(xiāo)機(jī)制精確控制哪些觀察者接收通知。不像全局事件總線所有監(jiān)聽(tīng)器都收到所有事件需要自己判斷是否處理。觀察者模式讓傳播范圍收斂在業(yè)務(wù)語(yǔ)義內(nèi)如“訂單創(chuàng)建事件”只通知訂單相關(guān)監(jiān)聽(tīng)器。第二層傳播時(shí)機(jī)可控。setChanged()notifyObservers()的組合讓通知時(shí)機(jī)由業(yè)務(wù)邏輯驅(qū)動(dòng)而非被動(dòng)響應(yīng)。比如庫(kù)存服務(wù)可以設(shè)定“庫(kù)存低于閾值時(shí)才通知采購(gòu)系統(tǒng)”而不是每次扣減都發(fā)通知。第三層傳播內(nèi)容可控。事件對(duì)象封裝了通知所需的最小信息集避免觀察者越權(quán)訪問(wèn)被觀察者內(nèi)部狀態(tài)。這既是安全邊界也是演進(jìn)緩沖——當(dāng)被觀察者內(nèi)部重構(gòu)時(shí)只要事件對(duì)象不變所有觀察者無(wú)需修改。所以當(dāng)你看到“設(shè)計(jì)模式 簡(jiǎn)單工廠模式”、“mvc設(shè)計(jì)模式”這些熱搜詞時(shí)要意識(shí)到所有設(shè)計(jì)模式都在解決同一類(lèi)問(wèn)題——如何讓軟件系統(tǒng)在變化中保持穩(wěn)定。工廠模式解決對(duì)象創(chuàng)建的變化MVC解決界面與邏輯的變化而觀察者模式解決的是依賴關(guān)系的變化。最后分享個(gè)小技巧下次評(píng)審代碼時(shí)看到一個(gè)類(lèi)里有超過(guò)3個(gè)if-else判斷不同業(yè)務(wù)場(chǎng)景并執(zhí)行不同操作問(wèn)問(wèn)自己“這些分支能不能變成觀察者”往往答案是肯定的。把條件分支轉(zhuǎn)化成事件訂閱代碼會(huì)立刻變得像呼吸一樣自然——這才是設(shè)計(jì)模式該有的樣子。