觀察者模式是行為型設計模式的基石,定義了物件間「一對多」的依賴關係,當主題狀態變更時自動通知所有觀察者。本文以演進式視角,從最基礎的手寫實現、多事件解耦、JDK 內建 API、Quartz 實戰風格,層層遞進至 Guava EventBus 的現代化架構思維,完整呈現觀察者模式在工程實踐中的成長路徑。
核心角色:
update(Subject) 回調方法。// 核心介面
interface IObservable {
void registerObserver(IObserver o);
void unregisterObserver(IObserver o);
void notifyObservers();
String getState();
void changeState();
void close();
}
interface IObserver {
void update(IObservable observable); // 直接傳遞主題引用
}
// 具體實現
class Observable implements IObservable {
private final List observers = Collections.synchronizedList(new ArrayList<>());
private String state = "NEW";
public void registerObserver(IObserver o) { observers.add(o); }
public void unregisterObserver(IObserver o) { observers.remove(o); }
public void notifyObservers() {
for (IObserver o : observers) o.update(this); // 遍歷通知
}
// ... 狀態變更時呼叫 notifyObservers()
}
class Observer implements IObserver {
private final String name;
public Observer(String name) { this.name = name; }
public void update(IObservable obs) {
System.out.println(name + " 收到狀態變更: " + obs.getState());
}
}
缺陷:觀察者強耦合具體 Subject,且所有觀察者收到相同通知,無法針對特定事件訂閱,違反「介面隔離原則」。
引入 Event 物件 作為通知載體,Subject 維護 Map,觀察者針對特定事件註冊。
interface IEvent {
void eventChange();
String getState();
}
class EventA implements IEvent { /* 狀態: INITIALIZED -> PENDING */ }
class EventB implements IEvent { /* 狀態: NEW -> IDLE */ }
interface IObservable {
void registerObserver(IEvent event, IObserver observer);
void unregisterObserver(IEvent event, IObserver observer);
void notifyObservers(IEvent event);
IEvent getEventA();
IEvent getEventB();
void changeStateA(); // 觸發 EventA
void changeStateB(); // 觸發 EventB
}
class Observable implements IObservable {
private final Map> eventObservers = new Hashtable<>();
private final EventA eventA = new EventA();
private final EventB eventB = new EventB();
public void registerObserver(IEvent event, IObserver o) {
eventObservers.computeIfAbsent(event, k -> Collections.synchronizedSet(new HashSet<>())).add(o);
}
public void notifyObservers(IEvent event) {
Set obs = eventObservers.get(event);
if (obs != null) obs.forEach(o -> o.update(event)); // 傳遞 Event 而非 Subject
}
// ...
}
interface IObserver {
void update(IEvent event); // 參數改為 Event
}
進步:觀察者只關心訂閱的事件,資訊隱藏得到改善。
殘留問題:事件類型硬編碼在 Subject 介面中(getEventA()、changeStateA()),新增事件需修改介面與實現類,違反「開閉原則」。
核心洞察:將「事件分發」職責從 Subject 移至獨立的 EventBus 元件。Subject 只負責發布事件,觀察者只負責訂閱感興趣事件,兩者完全不知彼此存在。
// Guava EventBus 風格簡化示意
class EventBus {
private final Map, Set> subscribers = new ConcurrentHashMap<>();
public void register(Object listener) {
// 掃描 @Subscribe 標註的方法,按參數類型(事件類型)分組註冊
}
public void post(Object event) {
// 根據事件類型查找訂閱者,同步/異步調用
}
}
// 使用範例
class OrderService {
private final EventBus eventBus;
public OrderService(EventBus bus) { this.eventBus = bus; }
public void createOrder(Order order) {
// 業務邏輯...
eventBus.post(new OrderCreatedEvent(order)); // 發布事件,無感知觀察者
}
}
class NotificationListener {
@Subscribe
public void onOrderCreated(OrderCreatedEvent e) { /* 發送通知 */ }
}
優勢:
✅ 完全解耦:發布者/訂閱者零依賴
✅ 類型安全:基於事件類型自動路由
✅ 支援異步/同步、死信隊列、事件繼承多態訂閱
✅ 符合「單一職責」與「開閉原則」
java.util.Observable (類) + java.util.Observer (介面),Java 9 標記 @Deprecated,原因:
❌ Observable 是類而非介面,限制繼承
❌ 同步通知、無異步支援
❌ 觀察者順序不可控、無事件類型區分
Quartz 針對排程領域定義 SchedulerListener 介面,僅包含 jobScheduled(Trigger)、jobUnScheduled(Trigger) 兩個回調,QuartzScheduler 內部維護 List,手動同步管理。展示了「領域驅動設計」中為特定業務場景裁剪觀察者介面的實踐。
| 場景 | 推薦方案 | 核心理由 |
|---|---|---|
| 簡單單一事件、極少觀察者、無擴展需求 | 手寫基礎版 / JDK Observable(舊專案) | 零依賴、極簡 |
| 多事件類型、觀察者關注點不同、需資訊隱藏 | 多事件 Map 版 / 手寫 EventBus | 事件粒度控制、介面隔離 |
| 企業級應用、微服務內部事件驅動、需異步/重試/順序保證 | Guava EventBus / Spring ApplicationEvent / Kafka/RocketMQ | 生產級特性、生態完善、架構解耦 |
| 跨進程/分散式事件通知 | 消息隊列 (Kafka, RabbitMQ, Pulsar) | 持久化、高可用、擴展性 |
架構感悟:從手寫 List 到 Guava EventBus,本質是「職責分離」的不斷深化——將「誰關心什麼事件」與「事件如何分發」從業務邏輯中剝離,交給專門的基礎設施元件。這正是現代框架(Spring Events、Netty ChannelPipeline、Vert.x EventBus)的核心設計哲學。