觀察者模式:定義物件間一對多依賴,當一個物件(主題)狀態改變時,所有依賴它的物件(觀察者)都會自動收到通知並更新。
核心優勢:主題與觀察者鬆耦合——主題不需知道觀察者具體類別,只知其實現 Observer 介面;支援動態訂閱/退訂;實現狀態同步傳播。
| 角色 | 類別/介面 | 職責 |
|---|---|---|
| Subject | 介面/抽象類 | 管理觀察者清單:addObserver、removeObserver、notifyObservers |
| ConcreteSubject | 類別 | 持有具體狀態,狀態變更時呼叫 notifyObservers |
| Observer | 介面 | 定義 update(Observable, Object) 回呼方法 |
| ConcreteObserver | 類別 | 實現具體回應邏輯 |
注意:Java 9 起標記為 @Deprecated,建議改用 PropertyChangeListener、Flow API (Reactive Streams) 或第三方庫。但理解其機制仍具教學價值。
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); // 推送新聞物件給所有觀察者
// 後續處理:存資料庫、推送推播...
}
}
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() + "》");
}
}
public class News {
private String title, body;
public News(String title, String body) { this.title = title; this.body = body; }
// getter/setter 略
}
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》
更靈活、不受單一繼承限制、支援泛型:
public interface Subject {
void addObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
public interface Observer {
void update(Subject subject);
}
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);
}
}
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);
}
}
}
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);
}
}
| 模式 | notifyObservers 參數 | 優點 | 缺點 |
|---|---|---|---|
| 推模式 | 傳遞具體資料 notifyObservers(data) | 觀察者無需知曉主題細節,資料即時送達 | 資料結構固定,觀察者被動接收,可能不需要所有資料 |
| 拉模式 | 傳遞主題本身 notifyObservers(this) | 觀察者按需「拉取」所需資料,彈性高 | 觀察者需依賴具體主題類別,耦合度較高 |
JDK Observable 採用推模式(可傳 Object 參數);手寫版常採用拉模式(傳 Subject 參考)。
Publisher/Subscriber 實現 Reactive Streams 規範觀察者模式是事件驅動架構的基石。從 JDK 內建到手寫實現,再到現代響應式框架,核心精神始終是「主題狀態變更 → 自動通知依賴者」的解耦傳播機制。掌握其推/拉模式權衡、記憶體洩漏風險(觀察者未移除)、同步/非同步通知選擇,是構建健壯事件系統的關鍵。