計(jì)模式實(shí)戰(zhàn):Event Aggregator(事件聚合器)模式深度解析 —— 基于 java-design-patterns 開(kāi)源倉(cāng)庫(kù))
示例工程教程【免費(fèi)下載鏈接】java-design-patternsDesign patterns implemented in Java項(xiàng)目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns點(diǎn)擊查看免費(fèi)下載Event Aggregator事件聚合器是一種結(jié)構(gòu)型設(shè)計(jì)模式用于解決“多個(gè)事件源 多個(gè)事件類(lèi)型”場(chǎng)景下客戶(hù)端訂閱邏輯爆炸的問(wèn)題它將眾多事件源的事件注冊(cè)與分發(fā)集中到一個(gè)聚合器對(duì)象上客戶(hù)端只需面向聚合器注冊(cè)一次即可接收來(lái)自所有源頭的事件。本文以 java-design-patterns 倉(cāng)庫(kù)中的 event-aggregator 模塊 為藍(lán)本結(jié)合完整源碼、測(cè)試用例與運(yùn)行輸出講解該模式的核心思想、參與者角色、編程式實(shí)現(xiàn)、事件流轉(zhuǎn)時(shí)序與適用場(chǎng)景讓讀者既能看懂模式原理也能直接照抄示例落地到自己的事件驅(qū)動(dòng)系統(tǒng)中。一、模式動(dòng)機(jī)為什么需要事件聚合器當(dāng)一個(gè)系統(tǒng)中有大量對(duì)象都可能產(chǎn)生事件時(shí)客戶(hù)端若想訂閱這些事件會(huì)面臨兩個(gè)層面的復(fù)雜度發(fā)現(xiàn)成本高客戶(hù)端必須逐個(gè)找到所有事件源并分別完成注冊(cè)訂閱數(shù)量爆炸如果每個(gè)事件源還暴露多個(gè)事件類(lèi)型那么每個(gè)事件都要單獨(dú)訂閱一次注冊(cè)代碼量隨“源數(shù)量 × 事件數(shù)量”線性膨脹。Event Aggregator 的解決思路是讓聚合器成為眾多事件源的“單一事件出口”。聚合器自身向所有事件源注冊(cè)客戶(hù)端只面向聚合器注冊(cè)從而把復(fù)雜的多點(diǎn)訂閱收斂為單點(diǎn)訂閱。這種收攏同時(shí)帶來(lái)一個(gè)附帶收益當(dāng)觀察者數(shù)量眾多、生命周期復(fù)雜時(shí)聚合器也簡(jiǎn)化了觀察者的內(nèi)存管理問(wèn)題取消注冊(cè)只發(fā)生在一處。二、理解模式的一個(gè)經(jīng)典類(lèi)比原文檔用一個(gè)《冰與火之歌》式的隱喻來(lái)解釋該模式見(jiàn) localization/es/event-aggregator/README.md國(guó)王 Joffrey 坐在鐵王座上統(tǒng)治七大王國(guó)他接收的大部分關(guān)鍵情報(bào)都來(lái)自“國(guó)王之手”Hand of the King即二把手。而國(guó)王之手身邊有許多親近的顧問(wèn)這些顧問(wèn)持續(xù)向他匯報(bào)王國(guó)內(nèi)發(fā)生的事件。在這個(gè)類(lèi)比中國(guó)王是最終消費(fèi)者觀察者國(guó)王之手是事件聚合器眾多顧問(wèn)情報(bào)源是事件生產(chǎn)者。國(guó)王不需要逐個(gè)聯(lián)系所有顧問(wèn)只需與國(guó)王之手保持溝通國(guó)王之手負(fù)責(zé)匯總各路情報(bào)并轉(zhuǎn)呈國(guó)王。一句話(huà)概括該模式事件聚合器是一個(gè)事件中介它收集來(lái)自多個(gè)源的事件并分發(fā)給已注冊(cè)的觀察者。三、參與者與角色劃分從源碼結(jié)構(gòu)看event-aggregator/src/main/java/com/iluwatar/event/aggregator該模塊包含 10 個(gè)類(lèi)/枚舉角色劃分如下角色類(lèi)名職責(zé)觀察者接口EventObserver定義事件消費(fèi)契約只有一個(gè)方法onEvent(Event e)事件發(fā)射器基類(lèi)EventEmitter維護(hù)Event → 觀察者列表的注冊(cè)表提供注冊(cè)與通知的通用機(jī)制事件類(lèi)型枚舉Event定義系統(tǒng)內(nèi)全部業(yè)務(wù)事件WHITE_WALKERS_SIGHTED、STARK_SIGHTED、WARSHIPS_APPROACHING、TRAITOR_DETECTED時(shí)間驅(qū)動(dòng)枚舉Weekday定義周一至周日作為事件發(fā)射的“時(shí)鐘”聚合器KingsHand既是觀察者訂閱下屬的事件又是發(fā)射器把事件轉(zhuǎn)呈給國(guó)王承擔(dān)事件中轉(zhuǎn)最終觀察者KingJoffrey只訂閱國(guó)王之手這一個(gè)聚合器消費(fèi)所有事件事件源LordBaelish、LordVarys、Scout各自在特定星期發(fā)射特定事件角色之間的關(guān)系可以概括為所有事件源LordBaelish、LordVarys、Scout把事件發(fā)給KingsHandKingsHand聚合器再把事件統(tǒng)一轉(zhuǎn)發(fā)給KingJoffrey除Scout外多數(shù)參與者既實(shí)現(xiàn)了EventObserver接口又繼承了EventEmitter基類(lèi)——這正體現(xiàn)了聚合器“既是消費(fèi)者又是生產(chǎn)者”的雙重身份。四、核心機(jī)制源碼剖析4.1 觀察者契約EventObserverEventObserver.java 是全模式最精簡(jiǎn)的接口只聲明一個(gè)方法public interface EventObserver { void onEvent(Event e); }所有希望接收事件的類(lèi)都必須實(shí)現(xiàn)它。在示例中KingJoffrey用它打印收到的情報(bào)Slf4j public class KingJoffrey implements EventObserver { Override public void onEvent(Event e) { LOGGER.info(Received event from the Kings Hand: {}, e.toString()); } }4.2 事件定義Event 與 WeekdayEvent.java 用枚舉固定了系統(tǒng)的全部事件類(lèi)型并通過(guò)toString()返回可讀描述RequiredArgsConstructor public enum Event { WHITE_WALKERS_SIGHTED(White walkers sighted), STARK_SIGHTED(Stark sighted), WARSHIPS_APPROACHING(Warships approaching), TRAITOR_DETECTED(Traitor detected); private final String description; Override public String toString() { return description; } }Weekday.java 同樣是一個(gè)帶描述文本的枚舉定義了MONDAY到SUNDAY它作為時(shí)間推進(jìn)的輸入?yún)?shù)決定哪個(gè)事件源在“哪天”發(fā)事件。4.3 注冊(cè)與通知的通用底座EventEmitterEventEmitter.java 是模式的核心機(jī)制所在。它用MapEvent, ListEventObserver維護(hù)“事件類(lèi)型 → 觀察者列表”的注冊(cè)表并提供三個(gè)關(guān)鍵能力public abstract class EventEmitter { private final MapEvent, ListEventObserver observerLists; public EventEmitter() { observerLists new HashMap(); } // 便捷構(gòu)造創(chuàng)建時(shí)即注冊(cè)一個(gè)默認(rèn)觀察者 public EventEmitter(EventObserver obs, Event e) { this(); registerObserver(obs, e); } // 按事件類(lèi)型注冊(cè)觀察者自動(dòng)初始化列表并去重 public final void registerObserver(EventObserver obs, Event e) { if (!observerLists.containsKey(e)) { observerLists.put(e, new LinkedList()); } if (!observerLists.get(e).contains(obs)) { observerLists.get(e).add(obs); } } // 通知指定事件的所有觀察者 protected void notifyObservers(Event e) { if (observerLists.containsKey(e)) { observerLists.get(e).forEach(observer - observer.onEvent(e)); } } // 由子類(lèi)實(shí)現(xiàn)時(shí)間推進(jìn)時(shí)的“事件觸發(fā)點(diǎn)” public abstract void timePasses(Weekday day); }值得注意的實(shí)現(xiàn)細(xì)節(jié)按事件粒度注冊(cè)注冊(cè)表以Event為鍵意味著同一個(gè)觀察者可以針對(duì)不同事件被多次注冊(cè)也意味著同一個(gè)事件可以有多個(gè)觀察者形成事件維度的多對(duì)多關(guān)系去重保護(hù)registerObserver在加入前用contains(obs)檢查避免同一觀察者對(duì)同一事件重復(fù)注冊(cè)導(dǎo)致重復(fù)通知空安全通知notifyObservers先檢查containsKey(e)對(duì)未注冊(cè)事件靜默跳過(guò)不會(huì)拋異常final修飾注冊(cè)方法注冊(cè)邏輯被固化在基類(lèi)子類(lèi)只能通過(guò)重寫(xiě)timePasses和onEvent表達(dá)業(yè)務(wù)差異保證了注冊(cè)機(jī)制的穩(wěn)定統(tǒng)一。4.4 聚合器實(shí)現(xiàn)KingsHandKingsHand.java 是模式的“樞紐節(jié)點(diǎn)”。它同時(shí)繼承EventEmitter作為發(fā)射器并實(shí)現(xiàn)EventObserver作為觀察者其onEvent方法直接把收到的事件原樣轉(zhuǎn)發(fā)給下游注冊(cè)者public class KingsHand extends EventEmitter implements EventObserver { public KingsHand() {} public KingsHand(EventObserver obs, Event e) { super(obs, e); } Override public void onEvent(Event e) { notifyObservers(e); } Override public void timePasses(Weekday day) { // 空實(shí)現(xiàn)聚合器不直接產(chǎn)生事件只做中轉(zhuǎn) } }從源碼結(jié)構(gòu)看KingsHand的onEvent就是整條事件鏈路的“轉(zhuǎn)發(fā)點(diǎn)”上游事件源調(diào)用它的onEvent它隨即調(diào)用notifyObservers把事件派發(fā)給下游觀察者。而它的timePasses被有意留空并在注釋中說(shuō)明“聚合器不直接處理基于時(shí)間的事件只是為滿(mǎn)足接口契約而占位”——這從反面印證了聚合器“只中轉(zhuǎn)、不生產(chǎn)”的角色定位。五、事件源實(shí)現(xiàn)誰(shuí)在什么時(shí)候發(fā)什么事件三個(gè)事件源類(lèi)都繼承EventEmitter通過(guò)重寫(xiě)timePasses(Weekday)實(shí)現(xiàn)“特定星期觸發(fā)特定事件”的調(diào)度邏輯LordVarysLordVarys.java——每周六發(fā)現(xiàn)內(nèi)奸Override public void timePasses(Weekday day) { if (day Weekday.SATURDAY) { notifyObservers(Event.TRAITOR_DETECTED); } }ScoutScout.java——周二發(fā)現(xiàn)戰(zhàn)船逼近、周三發(fā)現(xiàn)異鬼Override public void timePasses(Weekday day) { if (day Weekday.TUESDAY) { notifyObservers(Event.WARSHIPS_APPROACHING); } if (day Weekday.WEDNESDAY) { notifyObservers(Event.WHITE_WALKERS_SIGHTED); } }LordBaelishLordBaelish.java——每周五發(fā)現(xiàn)史塔克家族蹤跡Override public void timePasses(Weekday day) { if (day Weekday.FRIDAY) { notifyObservers(Event.STARK_SIGHTED); } }可見(jiàn)事件源的公共模式是在timePasses內(nèi)判斷星期命中特定Weekday時(shí)調(diào)用notifyObservers(Event.XXX)廣播事件。若子類(lèi)也實(shí)現(xiàn)了EventObserver如LordVarys則同樣可以在onEvent中繼續(xù)轉(zhuǎn)發(fā)形成多級(jí)鏈條。六、組裝與運(yùn)行完整示例拆解App.java 的main方法完整演示了如何搭建這條事件鏈。原文檔給出了核心片段這里結(jié)合源碼逐段解讀var kingJoffrey new KingJoffrey(); var kingsHand new KingsHand(); kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED); kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED); kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING); kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED); var varys new LordVarys(); varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED); varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED); var scout new Scout(); scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING); scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED); var baelish new LordBaelish(kingsHand, Event.STARK_SIGHTED);這里需要強(qiáng)調(diào)的組裝要點(diǎn)最終消費(fèi)者只訂閱聚合器KingJoffrey只與KingsHand建立注冊(cè)關(guān)系對(duì)上游的LordBaelish、LordVarys、Scout一無(wú)所知這正是“面向單一出口訂閱”的體現(xiàn)聚合器訂閱所有事件源KingsHand分別注冊(cè)到LordVarysTRAITOR_DETECTED、WHITE_WALKERS_SIGHTED、ScoutWARSHIPS_APPROACHING、LordBaelishSTARK_SIGHTED雙參數(shù)構(gòu)造器簡(jiǎn)化注冊(cè)new LordBaelish(kingsHand, Event.STARK_SIGHTED)等價(jià)于先new LordBaelish()再調(diào)用registerObserver(kingsHand, Event.STARK_SIGHTED)利用了EventEmitter的便捷構(gòu)造方法多級(jí)鏈條可以存在scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED)讓Scout的“異鬼事件”既直送KingsHand又經(jīng)LordVarys轉(zhuǎn)發(fā)展示了事件可以在觀察者之間接力傳播。驅(qū)動(dòng)事件流轉(zhuǎn)的核心循環(huán)使用流式 API把“每個(gè)發(fā)射器 × 每個(gè)星期”組合遍歷var emitters List.of(kingsHand, baelish, varys, scout); Arrays.stream(Weekday.values()) .Consumer? super EventEmittermap(day - emitter - emitter.timePasses(day)) .forEachOrdered(emitters::forEach);這段代碼的含義是依次取出周一至周日對(duì)emitters列表中的每個(gè)發(fā)射器調(diào)用timePasses(day)從而觸發(fā)各事件源在其“專(zhuān)屬日”廣播事件再由KingsHand匯總轉(zhuǎn)發(fā)給KingJoffrey。運(yùn)行方式在倉(cāng)庫(kù)根目錄執(zhí)行 Maven 命令即可運(yùn)行該模塊./mvnw -pl event-aggregator compile exec:java -Dexec.mainClasscom.iluwatar.event.aggregator.App若本機(jī)已安裝 Maven也可直接使用mvn替代./mvnw。運(yùn)行輸出原文檔給出了示例運(yùn)行后的控制臺(tái)輸出事件經(jīng)聚合器統(tǒng)一轉(zhuǎn)呈給國(guó)王18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the Kings Hand: Warships approaching 18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the Kings Hand: White walkers sighted 18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the Kings Hand: Stark sighted 18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the Kings Hand: Traitor detected四條日志全部以 “Received event from the Kings Hand” 開(kāi)頭直觀證明最終消費(fèi)者收到的每一條事件都來(lái)自唯一的聚合器KingsHand事件源與消費(fèi)者之間沒(méi)有直接耦合。七、事件流轉(zhuǎn)時(shí)序結(jié)合 event-aggregator/etc/event-aggregator-sequence-diagram.png 與源碼調(diào)用關(guān)系一次完整的事件流轉(zhuǎn)可概括為四個(gè)階段觸發(fā)App.main遍歷Weekday對(duì)每個(gè)發(fā)射器調(diào)用timePasses(day)廣播事件源如周六的LordVarys在timePasses內(nèi)命中條件調(diào)用notifyObservers(Event.TRAITOR_DETECTED)聚合轉(zhuǎn)發(fā)注冊(cè)表中該事件的觀察者KingsHand收到onEvent(...)調(diào)用立即再次調(diào)用自己的notifyObservers(e)消費(fèi)KingsHand的下游觀察者KingJoffrey的onEvent被觸發(fā)打印日志完成消費(fèi)。整個(gè)過(guò)程是“生產(chǎn)者 → 聚合器 → 消費(fèi)者”的兩段式傳遞聚合器是唯一的中間人。八、類(lèi)結(jié)構(gòu)總覽上圖來(lái)源 event-aggregator/etc/classes.png展示了該模塊的完整靜態(tài)結(jié)構(gòu)EventEmitter作為抽象基類(lèi)與EventObserver接口之間存在“維護(hù) 0..* 個(gè)觀察者”的關(guān)聯(lián)承擔(dān)注冊(cè)與通知KingsHand、LordVarys等業(yè)務(wù)類(lèi)繼承EventEmitter其中KingsHand、LordVarys還實(shí)現(xiàn)EventObserver接口兼具觀察者身份Event枚舉定義四種業(yè)務(wù)事件Weekday枚舉提供時(shí)間上下文二者共同支撐“時(shí)間驅(qū)動(dòng)的多源事件分發(fā)”。九、測(cè)試驗(yàn)證行為如何被保證模塊內(nèi)置的測(cè)試驗(yàn)證了模式行為的正確性見(jiàn) event-aggregator/src/test/java/com/iluwatar/event/aggregator。核心測(cè)試基類(lèi) EventEmitterTest.java 用 Mockito 對(duì)觀察者打樁逐一驗(yàn)證專(zhuān)屬日觸發(fā)對(duì)Weekday.values()全周遍歷依次調(diào)用emitter.timePasses(weekday)在specialDay當(dāng)天斷言每個(gè)注冊(cè)觀察者都恰好收到一次onEvent(event)verify(observer, times(1)).onEvent(eq(event))非專(zhuān)屬日靜默其他日期斷言觀察者零交互verifyNoMoreInteractions(observers)確保事件不會(huì)錯(cuò)發(fā)兩種注冊(cè)路徑等價(jià)測(cè)試同時(shí)覆蓋“無(wú)默認(rèn)觀察者 手動(dòng) registerObserver”與“帶默認(rèn)觀察者的雙參構(gòu)造器”兩條注冊(cè)路徑保證便捷構(gòu)造與手動(dòng)注冊(cè)行為一致。此外AppTest.java、EventTest.java、WeekdayTest.java 等測(cè)試分別覆蓋入口執(zhí)行、枚舉描述文本與toString()行為構(gòu)成對(duì)模式的完整行為驗(yàn)證。十、適用場(chǎng)景與使用建議原文檔給出的適用性判斷是當(dāng)系統(tǒng)中存在大量潛在事件源時(shí)Event Aggregator 是很好的選擇。與其讓觀察者逐個(gè)注冊(cè)所有事件源不如把注冊(cè)邏輯集中到聚合器。除簡(jiǎn)化注冊(cè)外聚合器還簡(jiǎn)化了觀察者使用中的內(nèi)存管理問(wèn)題。結(jié)合源碼可以進(jìn)一步推導(dǎo)出適合該模式的特征事件源數(shù)量多、事件類(lèi)型多且客戶(hù)端只關(guān)心最終結(jié)果不關(guān)心具體來(lái)源需要為“N 個(gè)事件源 × M 個(gè)事件類(lèi)型”提供統(tǒng)一的訂閱入口降低客戶(hù)端注冊(cè)代碼量需要一處集中管理觀察者的注冊(cè)與注銷(xiāo)避免觀察者散落各事件源導(dǎo)致的內(nèi)存泄漏風(fēng)險(xiǎn)希望事件能在觀察者之間接力傳遞如Scout → LordVarys → KingsHand → KingJoffrey時(shí)聚合器天然支持多級(jí)轉(zhuǎn)發(fā)。需要注意的邊界基于源碼結(jié)構(gòu)推斷EventEmitter的注冊(cè)表是普通的HashMapLinkedList未做并發(fā)同步notifyObservers采用同步順序遍歷。因此該實(shí)現(xiàn)適用于單線程或?qū)?shí)時(shí)性要求不高的場(chǎng)景若在多線程環(huán)境中使用需要自行補(bǔ)充線程安全措施。十一、與相關(guān)模式的對(duì)比原文檔明確指出 Event Aggregator 與Observer觀察者模式緊密相關(guān)從結(jié)構(gòu)上看Event Aggregator 內(nèi)部正是借助觀察者機(jī)制EventObserver接口、注冊(cè)、通知實(shí)現(xiàn)的。二者的差異在于關(guān)注點(diǎn)Observer解決“一個(gè)對(duì)象狀態(tài)變化時(shí)如何通知多個(gè)依賴(lài)對(duì)象”的基本通信問(wèn)題關(guān)注點(diǎn)在一對(duì)多通知本身Event Aggregator解決“多個(gè)事件源 多個(gè)事件類(lèi)型如何被統(tǒng)一訂閱”的編排問(wèn)題關(guān)注點(diǎn)在于把多點(diǎn)訂閱收斂為單點(diǎn)訂閱可視為對(duì)觀察者機(jī)制在“多源匯聚”方向上的組合應(yīng)用。十二、總結(jié)Event Aggregator 用“一個(gè)中間人”化解了多事件源訂閱的復(fù)雜度事件源只需把事件交給聚合器消費(fèi)者只需向聚合器注冊(cè)中間的事件路由、注冊(cè)管理全部收斂到一個(gè)對(duì)象。通過(guò)本模塊源碼可以看到其完整落地方案——EventEmitter基類(lèi)提供按事件粒度的注冊(cè)表與通知機(jī)制KingsHand作為聚合器承擔(dān)轉(zhuǎn)發(fā)KingJoffrey作為純消費(fèi)者只依賴(lài)唯一事件出口。如果你正在設(shè)計(jì)事件驅(qū)動(dòng)的客戶(hù)端訂閱層且面臨“源多、事雜、訂閱亂”的問(wèn)題Event Aggregator 是一個(gè)值得優(yōu)先考慮的結(jié)構(gòu)型方案?jìng)}庫(kù)中的 event-aggregator 模塊 提供了可直接復(fù)用的參考實(shí)現(xiàn)。參考資料模塊入口文檔event-aggregator/README.md西班牙語(yǔ)版原始文檔localization/es/event-aggregator/README.md核心源碼目錄event-aggregator/src/main/java/com/iluwatar/event/aggregator測(cè)試源碼目錄event-aggregator/src/test/java/com/iluwatar/event/aggregator類(lèi)圖與時(shí)序圖event-aggregator/etc/classes.png、event-aggregator/etc/event-aggregator-sequence-diagram.png贊分享示例工程教程【免費(fèi)下載鏈接】java-design-patternsDesign patterns implemented in Java項(xiàng)目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns點(diǎn)擊查看免費(fèi)下載相關(guān)推薦深入解析 Java 設(shè)計(jì)模式Event Aggregator 事件聚合器模式在 java-design-patterns 中的實(shí)現(xiàn)與應(yīng)用深入解析 Java 設(shè)計(jì)模式Event Aggregator 事件聚合器模式在 java design patterns 中的實(shí)現(xiàn)與應(yīng)用 事件聚合器Even示例工程教程java-design-patterns 之 Event Aggregator事件聚合器模式集中式事件管理的完整實(shí)戰(zhàn)指南java design patterns 之 Event Aggregator事件聚合器模式集中式事件管理的完整實(shí)戰(zhàn)指南 Event Aggregator示例工程教程Java 設(shè)計(jì)模式Front Controller 前端控制器模式深度解析基于 java-design-patterns 倉(cāng)庫(kù)Java 設(shè)計(jì)模式Front Controller 前端控制器模式深度解析基于 java design patterns 倉(cāng)庫(kù) 前端控制器Front C示例工程教程上一篇貓抓瀏覽器擴(kuò)展三分鐘掌握網(wǎng)頁(yè)視頻下載的終極解決方案下一篇RtspClientSharp高級(jí)教程TCP與UDP傳輸模式切換及性能優(yōu)化技巧創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考