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

Java 設計模式:觀察者模式完整實戰指南

     編輯:Java 相關知識

模式定義與核心價值

觀察者模式:定義物件間一對多依賴,當一個物件(主題)狀態改變時,所有依賴它的物件(觀察者)都會自動收到通知並更新。

核心優勢:主題與觀察者鬆耦合——主題不需知道觀察者具體類別,只知其實現 Observer 介面;支援動態訂閱/退訂;實現狀態同步傳播。

經典應用場景

  • GUI 事件處理(按鈕點擊、滑鼠移動、鍵盤輸入)
  • 訊息推送系統(新聞訂閱、股價異動、系統告警)
  • 模型-視圖同步(MVC 中 Model 變更通知 View 更新)
  • 快取失效/資料同步(分散式系統中的狀態廣播)

角色結構

角色類別/介面職責
Subject介面/抽象類管理觀察者清單:addObserver、removeObserver、notifyObservers
ConcreteSubject類別持有具體狀態,狀態變更時呼叫 notifyObservers
Observer介面定義 update(Observable, Object) 回呼方法
ConcreteObserver類別實現具體回應邏輯

JDK 內建支援:java.util.Observable / Observer

注意:Java 9 起標記為 @Deprecated,建議改用 PropertyChangeListener、Flow API (Reactive Streams) 或第三方庫。但理解其機制仍具教學價值。

實戰案例:新聞發布訂閱系統

1. 主題:新聞發布者


import java.util.Observable;

public class NewsPublisher extends Observable {
    public void publishNews(String title, String body) {
        News news = new News(title, body);
        setChanged(); // 關鍵:標記狀態已改變
        System.out.println("新聞發布:" + title);
        notifyObservers(news); // 推送新聞物件給所有觀察者
        // 後續處理:存資料庫、推送推播...
    }
}

2. 觀察者:訂閱者與管理者


import java.util.Observer;
import java.util.Observable;

public class SubscriberObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof News) {
            mailToSubscriber((News) arg);
        }
    }
    private void mailToSubscriber(News news) {
        System.out.println("郵件通知訂閱者:新新聞《" + news.getTitle() + "》");
    }
}

public class ManagerObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof News) {
            mailToManager((News) arg);
        }
    }
    private void mailToManager(News news) {
        System.out.println("郵件通知管理者:需審核新聞《" + news.getTitle() + "》");
    }
}

3. 資料實體


public class News {
    private String title, body;
    public News(String title, String body) { this.title = title; this.body = body; }
    // getter/setter 略
}

4. 客戶端測試


public class Client {
    public static void main(String[] args) {
        NewsPublisher publisher = new NewsPublisher();
        publisher.addObserver(new SubscriberObserver());
        publisher.addObserver(new ManagerObserver());
        
        publisher.publishNews("Hello News", "News body content");
    }
}
// 輸出:
// 新聞發布:Hello News
// 郵件通知管理者:需審核新聞《Hello News》
// 郵件通知訂閱者:新新聞《Hello News》

手寫實現:不依賴 JDK Observable

更靈活、不受單一繼承限制、支援泛型:

1. 自訂 Subject 介面


public interface Subject {
    void addObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

2. 自訂 Observer 介面


public interface Observer {
    void update(Subject subject);
}

3. 具體主題:整數資料包


import java.util.*;

public class IntegerDataBag implements Subject {
    private List data = new ArrayList<>();
    private List observers = new ArrayList<>();
    
    public void add(Integer i) { data.add(i); notifyObservers(); }
    public Iterator iterator() { return data.iterator(); }
    public Integer remove(int index) { 
        if (index < data.size()) { 
            Integer removed = data.remove(index); 
            notifyObservers(); 
            return removed; 
        } 
        return null; 
    }
    
    @Override public void addObserver(Observer o) { observers.add(o); }
    @Override public void removeObserver(Observer o) { observers.remove(o); }
    @Override public void notifyObservers() {
        for (Observer o : observers) o.update(this);
    }
}

4. 具體觀察者:加總器與列印器


public class IntegerAdder implements Observer {
    private IntegerDataBag bag;
    public IntegerAdder(IntegerDataBag bag) { this.bag = bag; bag.addObserver(this); }
    
    @Override public void update(Subject subject) {
        if (subject == bag) {
            int sum = 0;
            for (Integer i : bag) sum += i;
            System.out.println("資料變更,新總和:" + sum);
        }
    }
}

public class IntegerPrinter implements Observer {
    private IntegerDataBag bag;
    public IntegerPrinter(IntegerDataBag bag) { this.bag = bag; bag.addObserver(this); }
    
    @Override public void update(Subject subject) {
        if (subject == bag) {
            System.out.println("資料變更,內容:" + bag);
        }
    }
}

5. 驅動測試


public class Driver {
    public static void main(String[] args) {
        IntegerDataBag bag = new IntegerDataBag();
        for (int i = 1; i <= 8; i++) bag.add(i);
        
        new IntegerAdder(bag);
        new IntegerPrinter(bag);
        
        System.out.println("新增 9:");
        bag.add(9);
        
        System.out.println("移除索引 0:");
        bag.remove(0);
    }
}

進階主題:推 vs 拉 模式

模式notifyObservers 參數優點缺點
推模式傳遞具體資料 notifyObservers(data)觀察者無需知曉主題細節,資料即時送達資料結構固定,觀察者被動接收,可能不需要所有資料
拉模式傳遞主題本身 notifyObservers(this)觀察者按需「拉取」所需資料,彈性高觀察者需依賴具體主題類別,耦合度較高

JDK Observable 採用推模式(可傳 Object 參數);手寫版常採用拉模式(傳 Subject 參考)。

現代替代方案

  • JavaBeans PropertyChangeSupport:標準屬性變更通知機制
  • Flow API (Java 9+):Publisher/Subscriber 實現 Reactive Streams 規範
  • RxJava / Reactor:功能豐富的響應式程式庫
  • Event Bus (Guava EventBus, Spring ApplicationEvent):解耦發布/訂閱,支援異步、執行緒控制

總結

觀察者模式是事件驅動架構的基石。從 JDK 內建到手寫實現,再到現代響應式框架,核心精神始終是「主題狀態變更 → 自動通知依賴者」的解耦傳播機制。掌握其推/拉模式權衡、記憶體洩漏風險(觀察者未移除)、同步/非同步通知選擇,是構建健壯事件系統的關鍵。

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