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

Java 設計模式精講:觀察者模式原理、實現與 JDK 內置支持

     編輯:Java 綜合教學

Java 設計模式精講:觀察者模式原理、實現與 JDK 內置支持

觀察者模式是行為型設計模式中的明星模式,廣泛應用於事件驅動系統、GUI 開發、消息通知、數據同步等場景。本文將從模式定義、角色分析、代碼構造、實戰案例、JDK 內置支持等維度,全方位解析觀察者模式。

一、模式定義與核心思想

觀察者模式定義了對象間的一種一對多依賴關係,使得每當一個對象(主題/被觀察者)改變狀態時,所有依賴於它的對象(觀察者)都會得到通知並自動更新。

別名:發布-訂閱、模型-視圖、源-監聽器、從屬者模式。推薦使用 Publisher-Subscriber 術語,清晰劃分邊界。

典型應用場景:

  • RSS 訂閱、郵件通知、消息推送
  • 數據庫更新觸發緩存刷新
  • Android 廣播機制、Swing 事件監聽
  • Spring ApplicationEvent、Guava EventBus

二、四大角色深度解析

角色別名職責實現形式
抽象主題Subject / Observable維護觀察者集合;提供註冊/移除/通知接口接口或抽象類
抽象觀察者Observer定義更新接口 (update/operate)接口、抽象類或具體類
具體主題ConcreteSubject保存狀態;狀態變化時調用通知方法具體類
具體觀察者ConcreteObserver持有主題引用;實現更新接口,同步狀態具體類

三、代碼構造核心契約

  1. Subject 提供 addObserver()、removeObserver() 註冊管理接口
  2. Observer 提供 update() 接收通知接口
  3. 狀態變化時,Subject 遍歷集合調用 observer.update()
  4. 拉模型:Observer 主動從 Subject 獲取狀態
  5. 推模型:Subject 將狀態作為參數傳遞給 update(state)

四、實戰案例:貨車出庫通知系統

場景:載有鈔票黃金的貨車出發,需通知特警、保安;誤將電話發給盜賊。三方均為觀察者,發送短信接口為主題。

1. 觀察者接口

public interface ObserverMethod {
    void update();  // 拉模型:觀察者主動獲取詳情
}

2. 三個具體觀察者

// 保安
public class SecurityGuard implements ObserverMethod {
    @Override public void update() {
        System.out.println("車號:閩AAA888,大量現金黃金,保安提高警惕,貼身保護");
    }
}

// 盜賊
public class Thief implements ObserverMethod {
    @Override public void update() {
        System.out.println("車號:閩AAA888,大量現金黃金,準備炸彈,大幹一票");
    }
}

// 特警
public class SWAT implements ObserverMethod {
    @Override public void update() {
        System.out.println("車號:閩AAA888,大量現金黃金,特警提高警惕,貼身保護");
    }
}

3. 主題接口

public interface Watcher {
    void addWatcher(ObserverMethod watcher);
    void removeWatcher(ObserverMethod watcher);
    void notifyWatchers();  // 觸發通知
}

4. 具體主題實現

public class OperateWatcher implements Watcher {
    private List observers = new ArrayList<>();
    
    @Override
    public synchronized void addWatcher(ObserverMethod watcher) {
        if (watcher == null) throw new NullPointerException();
        if (!observers.contains(watcher)) observers.add(watcher);
    }
    
    @Override
    public void removeWatcher(ObserverMethod watcher) {
        observers.remove(watcher);
    }
    
    @Override
    public void notifyWatchers() {
        if (observers != null) {
            for (ObserverMethod observer : observers) {
                if (observer != null) observer.update();
            }
        }
    }
}

5. 測試驗證

public class ObserverTest {
    public static void main(String[] args) {
        Watcher watcher = new OperateWatcher();
        ObserverMethod guard = new SecurityGuard();
        ObserverMethod thief = new Thief();
        ObserverMethod swat = new SWAT();
        
        System.out.println("=== 全部通知 ===");
        watcher.addWatcher(guard);
        watcher.addWatcher(thief);
        watcher.addWatcher(swat);
        watcher.notifyWatchers();
        
        System.out.println("\n=== 移除盜賊後 ===");
        watcher.removeWatcher(thief);
        watcher.notifyWatchers();
    }
}

6. 輸出結果

=== 全部通知 ===
車號:閩AAA888,大量現金黃金,保安提高警惕,貼身保護
車號:閩AAA888,大量現金黃金,準備炸彈,大幹一票
車號:閩AAA888,大量現金黃金,特警提高警惕,貼身保護

=== 移除盜賊後 ===
車號:閩AAA888,大量現金黃金,保安提高警惕,貼身保護
車號:閩AAA888,大量現金黃金,特警提高警惕,貼身保護

五、進階:推模型實現

將具體通知內容作為參數傳遞,觀察者無需主動查詢:

// 觀察者接口擴展
public interface ObserverMethod {
    void update(String message);  // 推模型
}

// 主題通知時傳遞具體信息
public void notifyWatchers(String message) {
    for (ObserverMethod observer : observers) {
        if (observer != null) observer.update(message);
    }
}

// 調用
watcher.notifyWatchers("車號:閩AAA888,大量現金黃金,請注意安保");

六、JDK 內置觀察者模式支持

1. java.util.Observable / Observer (JDK 1.0+, JDK 9 棄用)

// 被觀察者繼承 Observable
public class WeatherData extends Observable {
    private float temperature;
    
    public void setTemperature(float temp) {
        this.temperature = temp;
        setChanged();      // 關鍵:標記狀態已變
        notifyObservers(temp); // 推模型傳遞數據
    }
}

// 觀察者實現 Observer 接口
public class CurrentDisplay implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            float temp = (Float) arg;
            display(temp);
        }
    }
}

缺點:Observable 是類而非接口,限制復用;setChanged() 為 protected,需繼承才能調用;JDK 9 標記棄用,建議遷移。

2. PropertyChangeSupport (JavaBeans 標準)

public class BeanSubject {
    private PropertyChangeSupport support = new PropertyChangeSupport(this);
    
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }
    
    public void setValue(String propertyName, Object oldValue, Object newValue) {
        support.firePropertyChange(propertyName, oldValue, newValue);
    }
}

3. 現代替代方案

  • Spring ApplicationEvent:ApplicationEventPublisher + @EventListener,支持異步、順序、事務綁定
  • Guava EventBus:EventBus.post(event) + @Subscribe,輕量級進程內事件總線
  • RxJava / Reactor:響應式流,Observable/Flowable,支持背壓、操作符鏈
  • Java 9+ Flow API:java.util.concurrent.Flow,標準化響應式規範

七、模式優缺點與最佳實踐

優點缺點/注意點
主題與觀察者鬆耦合觀察者過多時通知延遲,考慮異步通知
支持廣播通信循環依賴風險(A觀察B,B觀察A),需設計避免
符合開閉原則觀察者異常可能影響其他觀察者,需隔離異常處理
動態註冊/移除內存洩漏風險:觀察者未移除導致無法 GC,需弱引用或生命周期管理

最佳實踐清單

  1. 優先使用成熟框架事件機制,避免手寫基礎設施
  2. 明確選擇推/拉模型:高頻輕量用推,大對象低頻用拉
  3. 通知方法加同步或使用線程安全集合
  4. 提供批量註冊/移除 API,減少鎖競爭
  5. 異步通知場景使用 ExecutorService 或 CompletableFuture
  6. 記錄通知日誌,便於追蹤排查
  7. 文檔化事件語義:事件名、載荷結構、觸發時機、順序保證

總結

觀察者模式以「一對多、鬆耦合、自動通知」解決了對象間狀態同步的核心難題。從手寫接口到 JDK Observable,再到 Spring Event、Guava EventBus、響應式流,其核心思想始終未變——發布者不關心誰訂閱,訂閱者不關心誰發布,中間由中介者解耦。掌握觀察者模式,不僅是學會一種設計模式,更是理解現代事件驅動架構、響應式編程、微服務異步通信的基石。

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