寫給未來的自己:每次手敲事件模型都要 Google,干脆把思路和踩坑一次性記清楚。文章很長,都是嘮叨,目的是讓自己看兩眼就能把設計理由找回來。
目錄
- 為什么我要折騰事件模型?
- V0 ─ 單一事件的觀察者模式
- V1 ─ 多事件同步總線(類型拆分)
- V2 ─ 訂閱者優先級(鏈式調用可控)
- V3 ─ 事件優先級 + 異步(削峰 & 隔離)
- V4 ─ 組合式單線程總線(順序極致保證)
- 經驗小抄
1|為什么我要折騰事件模型?
- 耦合度:把 if?else 通知邏輯塞在同一個類里,一改就牽一大片,改怕了。
- 可測試性:希望能單測“發一個事件 → 看誰收到了”,不用啟動整套應用。
- 面試尬聊:被問到“Spring ApplicationEvent 和 Observer 有啥區別”,含含糊糊很挫。
這篇就是把一次次“為什么要這樣設計”寫進代碼注釋里,別再年年忘。
2|V0 ? 單一事件的觀察者模式
場景:只有一類消息,比如聊天窗口有人發言,監聽者立刻打印出來。
痛點:一旦要支持第二種事件,就得復制粘貼另一套接口。
// ========== MessageEvent ==========
// 最簡單的 POJO,只有一條內容。后面會發現 Event 越寫越胖,這里先別管。
public class MessageEvent {private final String content;public MessageEvent(String content) { this.content = content; }public String content() { return content; }
}// ========== Listener ==========
// 單方法接口,本質就是 Java 版回調。
public interface Listener {void onMessage(MessageEvent e);
}public class ConsolePrinter implements Listener {@Override public void onMessage(MessageEvent e) {// 業務寫死:收到就打印。只演示用。System.out.println("[Printer] " + e.content());}
}// ========== SimplePublisher ==========
// 最小發布者:僅負責遍歷列表,沒有任何順序控制。
public class SimplePublisher {private final List<Listener> listeners = new ArrayList<>();public void addListener(Listener l) { listeners.add(l); }public void publish(String msg) {MessageEvent e = new MessageEvent(msg);// 順序 = addListener 的順序。這里沒做保護性復制,線程安全靠調用方自覺。for (Listener l : listeners) l.onMessage(e);}
}
總結:
- 寫起來爽,讀起來爽,但一旦業務變復雜就原地報廢。
- 發布者對訂閱者的 具體類型 沒有依賴,但依賴了“只有一種事件”的假設。
3|V1 ? 多事件同步總線
目標:讓 Publisher 不關心 到底是哪種事件,把“事件?訂閱者”關系外提。
3.1 核心接口
/** 所有事件的父類,加時間戳是為了調試時知道誰先誰后。 */