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