首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 觀察者模式演進全景:從簡單回調到 Guava EventBus 架構設計

     編輯:Java 相關知識

模式本質

觀察者模式定義對象間一對多依賴,當一個對象(Subject/Observable)狀態改變,所有依賴它的對象(Observer)自動收到通知並更新。核心三要素:

  1. Subject:維護觀察者列表,提供註冊/移除/通知接口。
  2. Observer:定義更新接口 update()。
  3. ConcreteSubject/ConcreteObserver:具體業務實現。

階段一:最簡單實現(同步、單一事件)

interface IObserver { void update(IObservable source); }
interface IObservable {
    void register(IObserver o);
    void unregister(IObserver o);
    void notifyObservers();
    String getState();
}

class Observable implements IObservable {
    private final List<IObserver> observers = new CopyOnWriteArrayList<>(); // 線程安全
    private String state;

    public void register(IObserver o) { observers.add(o); }
    public void unregister(IObserver o) { observers.remove(o); }

    public void notifyObservers() {
        for (IObserver o : observers) o.update(this);
    }
    // 狀態變更觸發通知
    public void setState(String s) { this.state = s; notifyObservers(); }
}

class LoggerObserver implements IObserver {
    public void update(IObservable src) {
        System.out.println("Log: " + src.getState());
    }
}

缺點:所有觀察者收到相同通知,無法區分事件類型;Subject 暴露全部狀態,違反最小知識原則。

階段二:多事件分發(事件類型解耦)


interface IEvent { String getName(); String getState(); }
class EventA implements IEvent { ... }
class EventB implements IEvent { ... }

interface IObservable {
    void register(IEvent event, IObserver observer);
    void unregister(IEvent event, IObserver observer);
    void notify(IEvent event);
}

class Observable implements IObservable {
    private final Map<IEvent, Set<IObserver>> registry = new ConcurrentHashMap<>();
    private final EventA eventA = new EventA();
    private final EventB eventB = new EventB();

    public void register(IEvent e, IObserver o) {
        registry.computeIfAbsent(e, k -> ConcurrentHashMap.newKeySet()).add(o);
    }
    public void notify(IEvent e) {
        Set<IObserver> set = registry.get(e);
        if (set != null) set.forEach(o -> o.update(e));
    }
    // 觸發特定事件
    public void changeA() { eventA.change(); notify(eventA); }
}

缺點:事件類型硬編碼在 Subject 中,擴展新事件需修改接口與實現,違反開閉原則。

階段三:JDK 內置 java.util.Observable/Observer(已棄用 Java 9)


class MyObservable extends Observable {
    private String value;
    public void setValue(String v) {
        if (!Objects.equals(value, v)) {
            value = v;
            setChanged();        // 關鍵:標記已變更
            notifyObservers(v);  // 傳遞參數
        }
    }
}

class MyObserver implements Observer {
    public void update(Observable o, Object arg) {
        System.out.println("Received: " + arg);
    }
}

缺點:Observable 是類而非接口,限制繼承;setChanged() 為 protected,外部不可調用;通知順序固定;Java 9 標記棄用。

階段四:生產級框架實現 — Quartz SchedulerListener

Quartz 將監聽器按功能分類(SchedulerListener、JobListener、TriggerListener),每類獨立管理,接口定義細粒度回調:


// 核心調度器持有 List
public void addInternalSchedulerListener(SchedulerListener l) { ... }
public void notifySchedulerListenersScheduled(Trigger t) {
    for (SchedulerListener l : getInternalSchedulerListeners()) l.jobScheduled(t);
}

優點:職責單一、擴展性強、線程安全、生命周期清晰。

階段五:現代最佳實踐 — Guava EventBus(推薦)

引入事件總線作為中介者,徹底解耦發布者與訂閱者:

依賴

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.0.0-jre</version>
</dependency>

核心代碼


// 1. 定義事件對象(POJO,不可變)
class UserRegisteredEvent {
    final String userId;
    final String email;
    UserRegisteredEvent(String userId, String email) {
        this.userId = userId; this.email = email;
    }
}

// 2. 訂閱者:任意類,方法標註 @Subscribe
class EmailService {
    @Subscribe
    public void onUserRegistered(UserRegisteredEvent event) {
        sendWelcomeEmail(event.email);
    }
}

class AnalyticsService {
    @Subscribe
    public void onUserRegistered(UserRegisteredEvent event) {
        track("user_registered", event.userId);
    }
}

// 3. 發布者:注入 EventBus,post 事件
class UserService {
    private final EventBus eventBus;
    UserService(EventBus eventBus) { this.eventBus = eventBus; }

    public void register(String userId, String email) {
        // 保存用戶...
        eventBus.post(new UserRegisteredEvent(userId, email)); // 同步發布
    }
}

// 4. 組裝(Spring 配置示例)
@Configuration
class EventBusConfig {
    @Bean EventBus eventBus() { return new EventBus(); } // 同步
    // 異步: new AsyncEventBus(executor)
}

@Bean
EmailService emailService(EventBus bus) {
    EmailService svc = new EmailService();
    bus.register(svc);
    return svc;
}

EventBus 核心優勢

特性說明
完全解耦發布者不知訂閱者,訂閱者不知發布者,僅共享事件類
類型安全分發基於參數類型自動路由,無需字符串常量
同步/異步靈活EventBus 同步,AsyncEventBus 異步(自定義 Executor)
死碼檢測編譯期發現未被訂閱的事件、未註冊的 @Subscribe 方法
異常隔離單個訂閱者異常不影響其他訂閱者,可配置 SubscriberExceptionHandler
零侵入POJO 事件、普通方法,無需實現接口、繼承基類

架構選型建議

場景推薦方案
簡單模塊內狀態同步階段一:簡單接口 + CopyOnWriteArrayList
模塊間多事件通知階段二:事件類型 Map 分發
跨服務/跨模塊業務事件Guava EventBus (單體) / Spring ApplicationEvent (Spring 生態) / Kafka/RocketMQ (微服務)
高性能異步事件處理Guava AsyncEventBus / Disruptor / Reactor Flux
調度/框架擴展點Quartz 風格:分類 Listener 接口 + 優先級排序

總結

觀察者模式演進路徑:硬編碼列表 → 事件類型分發 → 框架標準化 → 事件總線中介者。現代 Java 開發中,Guava EventBus 是單體應用內部事件驅動的首選,兼顧解耦、類型安全、同步異步靈活、零侵入。微服務架構下,領域事件應通過消息中間件(Kafka 等)跨服務傳遞,配合 EventBus 做本地發布,構建完整的事件驅動架構(EDA)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved