? ? ? ?在軟件開發領域,設計模式堪稱開發者智慧的凝練結晶,它們為解決各類常見編程難題提供了行之有效的方案。觀察者模式(Observer Pattern)作為行為型設計模式的重要一員,在處理對象間依賴關系與事件通知方面表現卓越。本文將深入剖析 Java 中的觀察者模式,涵蓋其核心概念、實現途徑、優劣之處及適用場景,助力讀者全方位掌握并靈活運用這一關鍵設計模式。?
一、觀察者模式核心概念?
(一)定義與核心思想?
觀察者模式定義了對象間的一對多依賴關系,當某一對象(被觀察者,Subject)的狀態發生變動時,所有依賴于它的對象(觀察者,Observer)都會收到通知并自動更新。其核心在于解耦觀察者與被觀察者,讓二者借助抽象接口進行交互,而非直接耦合,以此提升系統的靈活性與可擴展性。?
(二)角色劃分?
觀察者模式主要涉及四個關鍵角色:?
- 抽象被觀察者(Subject):定義了添加、移除觀察者以及通知觀察者的接口或抽象類,維護著一個觀察者列表,當自身狀態變化時,負責通知列表中的所有觀察者。?
- 具體被觀察者(Concrete Subject):實現抽象被觀察者的接口,具體負責管理自身狀態,狀態變化時,調用通知方法告知觀察者。?
- 抽象觀察者(Observer):定義了用于接收被觀察者通知的更新接口,收到通知后執行相應操作。?
- 具體觀察者(Concrete Observer):實現抽象觀察者的更新接口,通常持有對具體被觀察者的引用,以便在收到通知時獲取被觀察者狀態并處理。?
二、Java 中觀察者模式的實現方式?
在 Java 里,實現觀察者模式主要有兩種常見途徑:一種是借助 JDK 內置的觀察者相關類與接口,另一種則是自定義實現。接下來將分別詳細闡述。?
(一)JDK 內置的觀察者模式實現?
Java 的 java.util 包提供了 Observable 類和 Observer 接口,方便快速實現觀察者模式。?
1. Observable 類?
Observable 類是抽象被觀察者的具體實現,具備以下主要方法:?
- addObserver(Observer o):向觀察者列表中添加一個觀察者。?
- deleteObserver(Observer o):從觀察者列表中移除一個觀察者。?
- notifyObservers():通知所有已注冊的觀察者,但調用此方法前需先調用setChanged()方法,用以標記被觀察者狀態已改變。?
- notifyObservers(Object arg):帶參數的通知方法,將參數傳遞給觀察者。?
- setChanged():設置內部標志,表明被觀察者狀態已改變,只有調用此方法后再調用notifyObservers(),觀察者才會收到通知。?
- clearChanged():清除狀態改變標志。?
2. Observer 接口?
Observer 接口定義了觀察者的更新方法,僅有一個方法:?
- update(Observable o, Object arg):當被觀察者狀態變化并通知觀察者時,該方法被調用。其中,o是發出通知的被觀察者對象,arg是傳遞給觀察者的參數(若有)。?
3. 使用步驟示例?
以簡單的天氣數據監測系統為例,展示如何運用 JDK 內置的觀察者模式。?
步驟 1:創建具體被觀察者(WeatherData)?
?
import java.util.Observable;?
?
public class WeatherData extends Observable {?
private float temperature;?
private float humidity;?
private float pressure;?
?
public void setWeatherData(float temperature, float humidity, float pressure) {?
this.temperature = temperature;?
this.humidity = humidity;?
this.pressure = pressure;?
// 設置狀態改變標志?
setChanged();?
// 通知所有觀察者?
notifyObservers();?
// 或者傳遞具體參數?
// notifyObservers(new WeatherInfo(temperature, humidity, pressure));?
}?
?
// 獲取溫度、濕度、氣壓的方法...?
}?
?
步驟 2:創建具體觀察者(CurrentConditionsDisplay)?
?
import java.util.Observable;?
import java.util.Observer;?
?
public class CurrentConditionsDisplay implements Observer {?
private float temperature;?
private float humidity;?
private Observable observable;?
?
public CurrentConditionsDisplay(Observable observable) {?
this.observable = observable;?
// 將當前觀察者添加到被觀察者的列表中?
observable.addObserver(this);?
}?
?
@Override?
public void update(Observable o, Object arg) {?
if (o instanceof WeatherData) {?
WeatherData weatherData = (WeatherData) o;?
this.temperature = weatherData.getTemperature();?
this.humidity = weatherData.getHumidity();?
display();?
}?
}?
?
private void display() {?
System.out.println("當前天氣狀況:溫度 " + temperature + "℃,濕度 " + humidity + "%");?
}?
}?
?
步驟 3:客戶端使用?
?
public class Client {?
public static void main(String[] args) {?
WeatherData weatherData = new WeatherData();?
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);?
?
// 模擬天氣數據變化?
weatherData.setWeatherData(25.0f, 60.0f, 1013.0f);?
weatherData.setWeatherData(28.0f, 55.0f, 1010.0f);?
}?
}?
?
4. 注意事項?
- Observable 類是具體類而非接口,這在一定程度上限制了其擴展性,因為 Java 不支持多繼承,若一個類已繼承其他類,便無法再繼承 Observable 類。?
- 調用 notifyObservers () 方法前,務必先調用 setChanged () 方法,否則觀察者不會收到通知。這是由于 Observable 類內部通過布爾變量 changed 判斷是否通知觀察者,setChanged () 方法將其設為 true,notifyObservers () 方法調用后會將其設為 false(除非使用帶參數版本且參數為 null)。?
(二)自定義實現觀察者模式?
盡管 JDK 提供了內置的觀察者實現,但在某些場景下,我們可能期望更靈活地定義抽象被觀察者和抽象觀察者接口,或者避免依賴 JDK 中的 Observable 類(比如被觀察者需繼承其他類時)。此時,可自定義實現觀察者模式。?
1. 定義抽象觀察者接口(Observer)?
?
public interface Observer {?
void update(Object data);?
}?
?
2. 定義抽象被觀察者接口(Subject)?
?
public interface Subject {?
void registerObserver(Observer observer);?
void removeObserver(Observer observer);?
void notifyObservers(Object data);?
}?
?
3. 實現具體被觀察者(ConcreteSubject)?
?
import java.util.ArrayList;?
import java.util.List;?
?
public class ConcreteSubject implements Subject {?
private List<Observer> observers = new ArrayList<>();?
private Object data;?
?
@Override?
public void registerObserver(Observer observer) {?
observers.add(observer);?
}?
?
@Override?
public void removeObserver(Observer observer) {?
observers.remove(observer);?
}?
?
@Override?
public void notifyObservers(Object data) {?
this.data = data;?
for (Observer observer : observers) {?
observer.update(data);?
}?
}?
?
public void setData(Object data) {?
notifyObservers(data);?
}?
}?
?
4. 實現具體觀察者(ConcreteObserver)?
?
public class ConcreteObserver implements Observer {?
private String name;?
?
public ConcreteObserver(String name) {?
this.name = name;?
}?
?
@Override?
public void update(Object data) {?
System.out.println("觀察者" + name + "接收到數據:" + data);?
}?
}?
?
5. 客戶端使用示例?
?
public class CustomObserverClient {?
public static void main(String[] args) {?
ConcreteSubject subject = new ConcreteSubject();?
ConcreteObserver observer1 = new ConcreteObserver("A");?
ConcreteObserver observer2 = new ConcreteObserver("B");?
?
subject.registerObserver(observer1);?
subject.registerObserver(observer2);?
?
subject.setData("新數據來了!");?
subject.removeObserver(observer2);?
subject.setData("再次更新數據!");?
}?
}?
?
(三)兩種實現方式的對比?
?
特性? | JDK 內置實現? | 自定義實現? |
抽象程度? | 使用具體類 Observable 和接口 Observer,Observable 設計存在局限性(如非接口)? | 完全自定義抽象接口,可依需求靈活設計接口方法? |
擴展性? | 因 Observable 是具體類,若被觀察者需繼承其他類則無法使用,擴展性受限? | 被觀察者實現自定義 Subject 接口,可自由繼承其他類,擴展性更佳? |
依賴關系? | 依賴 java.util 包中的類和接口? | 不依賴 JDK 特定類,可在更廣泛環境中使用? |
學習成本? | 簡單易用,適用于快速實現簡單觀察者模式場景? | 需手動定義接口和實現,適用于復雜場景或高度定制需求? |
?
三、觀察者模式的優缺點?
(一)優點?
- 解耦觀察者和被觀察者:觀察者與被觀察者通過抽象接口交互,彼此無需了解對方具體實現,降低代碼耦合度。被觀察者狀態變化時,觀察者自動接收通知,無需主動查詢,提升系統靈活性。?
- 支持廣播通信:被觀察者能同時通知多個觀察者,實現一對多通信模式,契合消息推送、狀態變更通知等事件廣播場景。?
- 提高可維護性和擴展性:觀察者與被觀察者分離,新增或修改觀察者行為不影響被觀察者代碼,反之亦然。系統更易維護和擴展,符合開閉原則(對擴展開放,對修改關閉)。?
(二)缺點?
- 通知順序問題:被觀察者通知觀察者時,通常按注冊順序依次調用更新方法。若觀察者間存在依賴關系或對通知順序有特定要求,可能需額外處理,否則易導致意外結果。?
- 潛在的性能問題:若觀察者數量眾多,或每個觀察者的更新方法執行耗時較長,被觀察者通知所有觀察者時可能耗時過多,引發性能下降,極端情況下甚至導致線程阻塞。?
- 過度使用可能導致復雜度增加:觀察者模式雖能解耦對象,但系統中過度使用會使對象間依賴關系復雜,難以追蹤和維護。尤其當多個被觀察者和觀察者存在復雜交互時,可能形成難以管理的網狀結構。?
四、觀察者模式的適用場景?
(一)當一個對象的狀態變化需要通知多個對象時?
這是觀察者模式最典型的應用場景。比如在股票交易系統中,某只股票價格變動時,需通知所有關注該股票的用戶;新聞訂閱系統里,有新新聞發布時,要通知所有訂閱該新聞頻道的用戶。?
(二)當系統需要實現事件驅動機制時?
觀察者模式可用于實現事件監聽與處理機制。以 GUI 編程為例,按鈕點擊事件、窗口關閉事件等,都可通過觀察者模式實現。用戶觸發事件(相當于被觀察者狀態變化)時,注冊的事件監聽器(相當于觀察者)接收通知并執行相應處理邏輯。?
(三)當需要解耦具有依賴關系的對象時?
若兩個或多個對象存在一對多的依賴關系,即一個對象變化影響多個對象,使用觀察者模式可將它們解耦,使其通過抽象接口交互,而非直接依賴具體實現,提升系統靈活性與可維護性。?
(四)當需要實現數據的實時更新和同步時?
在分布式系統或實時數據處理系統中,常需將數據變化實時同步到多個客戶端或模塊。觀察者模式能便捷實現數據實時更新,數據源(被觀察者)數據變化時,所有訂閱該數據源的客戶端(觀察者)自動接收通知并更新自身數據。?
五、實際應用案例分析?
(一)Swing 中的事件處理?
在 Java Swing GUI 開發中,觀察者模式廣泛應用于事件處理。例如,用戶點擊按鈕時,按鈕對象(被觀察者)通知所有注冊的動作監聽器(觀察者)。動作監聽器實現 ActionListener 接口(相當于抽象觀察者接口),其中的 actionPerformed 方法(相當于 update 方法)在按鈕點擊時被調用,執行相應事件處理邏輯。?
(二)Spring Framework 中的事件機制?
Spring 框架提供基于觀察者模式的事件發布 - 訂閱機制。通過 ApplicationEvent 類(抽象事件,相當于被觀察者狀態變化通知)和 ApplicationListener 接口(觀察者接口),開發者可自定義事件和事件監聽器。事件發生時,Spring 容器將事件發布給所有注冊監聽器,監聽器執行相應處理邏輯。該機制在 Spring Boot 的自動配置、事務管理等模塊均有應用。?
(三)消息中間件中的訂閱 - 發布模式?
消息中間件(如 Kafka、RabbitMQ 等)的訂閱 - 發布模式(Publish/Subscribe Pattern)與觀察者模式極為相似。生產者(相當于被觀察者)發布消息到主題(Topic),消費者(相當于觀察者)訂閱主題并接收消息。盡管訂閱 - 發布模式通常借助消息代理(Broker)作為中介,而觀察者模式中被觀察者和觀察者可直接交互,但二者核心思想均為一對多依賴關系和事件通知。?
六、總結與最佳實踐?
觀察者模式是極為實用的設計模式,通過解耦觀察者和被觀察者,實現靈活的事件通知機制,適用于多種場景。在 Java 中,既可用 JDK 內置的 Observable 和 Observer 快速實現簡單觀察者模式,也可通過自定義接口實現更靈活、貼合需求的模式。?
運用觀察者模式時,需留意以下最佳實踐:?
- 合理設計抽象接口:抽象被觀察者和抽象觀察者的接口應盡量通用,涵蓋所有可能操作,避免頻繁修改接口。?
- 控制觀察者數量和更新邏輯:避免注冊過多觀察者,或在觀察者更新方法中執行耗時操作,防止性能問題。?
- 處理循環依賴問題:若觀察者和被觀察者存在循環依賴(如觀察者更新時修改被觀察者狀態,導致再次通知觀察者),需謹慎處理,避免陷入無限循環。?
- 結合具體場景選擇實現方式:依據項目具體需求,選擇 JDK 內置實現或自定義實現。若需高度靈活性和擴展性,自定義實現更佳;若場景簡單,追求快速實現,JDK 內置實現更便捷。?
深入理解觀察者模式的原理、實現方式和適用場景,并遵循最佳實踐,開發者便能在軟件開發中靈活運用該模式,提升代碼質量與系統可維護性。隨著技術持續發展,觀察者模式也在不斷演進,與責任鏈模式、策略模式等結合,用以解決更復雜的問題。因此,持續學習和實踐設計模式,對提升軟件開發能力意義重大。