一、實現方式
- 自定義實現
通過手動定義Subject
和Observer
接口,實現一對多依賴關系:
// 觀察者接口
public interface Observer {void update(float temp, float humidity, float pressure);
}
// 主題接口
public interface Subject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}
// 具體主題類(天氣數據)
public class WeatherData implements Subject {private List observers = new ArrayList<>();private float temperature, humidity, pressure;@Overridepublic void registerObserver(Observer o) {observers.add(o);}@Overridepublic void removeObserver(Observer o) {observers.remove(o);}@Overridepublic void notifyObservers() {for (Observer o : observers) {o.update(temperature, humidity, pressure);}}public void setMeasurements(float temp, float hum, float press) {temperature = temp;humidity = hum;pressure = press;notifyObservers();}
}
特點:靈活可控,但需自行處理線程安全和內存管理。
- 使用JDK內置類(
Observable
與Observer
)
JDK提供的Observable
類和Observer
接口簡化實現,但需注意其設計缺陷(如需手動調用setChanged()
):
// 具體主題類
public class WeatherData extends Observable {private float temperature, humidity, pressure;public void setMeasurements(float temp, float hum, float press) {temperature = temp;humidity = hum;pressure = press;setChanged(); // 標記狀態變化notifyObservers(new MeasurementData(temp, hum, press)); // 推模型}
}
// 具體觀察者類
public class CurrentConditionsDisplay implements Observer {@Overridepublic void update(Observable o, Object arg) {if (arg instanceof MeasurementData) {MeasurementData data = (MeasurementData) arg;System.out.println("溫度:" + data.getTemp());}}
}
注意事項:
notifyObservers()
需在setChanged()
后調用,否則不觸發通知。- 觀察者需通過參數
arg
獲取數據(推模型)或從主題拉取數據(拉模型)。
- 高級實現(異步與線程安全)
通過線程池異步通知觀察者,避免阻塞主題線程:
public class AsyncSubject extends Subject {private ExecutorService executor = Executors.newFixedThreadPool(4);@Overridepublic void notifyObservers() {for (Observer o : observers) {executor.submit(() -> o.update(...));}}
}
優勢:提升并發性能,避免單線程阻塞。
二、測試方法
- 單元測試(Mockito框架)
使用Mockito模擬觀察者行為,驗證通知邏輯:
@Test
public void testObserverNotification() {// 1. 創建Mock觀察者Observer mockObserver = Mockito.mock(Observer.class);// 2. 注冊觀察者到主題WeatherData weatherData = new WeatherData();weatherData.registerObserver(mockObserver);// 3. 觸發狀態變化weatherData.setMeasurements(25.5f, 65, 1013.1f);// 4. 驗證觀察者方法是否被調用Mockito.verify(mockObserver).update(25.5f, 65, 1013.1f);
}
關鍵點:
- 使用
when()
設置Mock對象行為(如異常拋出)。 - 使用
verify()
驗證方法調用次數和參數。
- 集成測試(多線程場景)
測試觀察者模式在并發環境下的穩定性:
@Test
public void testConcurrentObservers() {WeatherData weatherData = new WeatherData();Observer observer1 = new CurrentConditionsDisplay();Observer observer2 = new StatisticsDisplay();// 多線程注冊觀察者Thread t1 = new Thread(() -> weatherData.registerObserver(observer1));Thread t2 = new Thread(() -> weatherData.registerObserver(observer2));t1.start();t2.start();// 等待線程結束t1.join();t2.join();// 觸發通知并驗證weatherData.setMeasurements(30f, 70, 1012.5f);// 驗證兩個觀察者均被通知
}
注意事項:
- 使用
synchronized
或并發集合(如CopyOnWriteArrayList
)保證線程安全。 - 測試觀察者是否因循環依賴導致死鎖。
- 性能測試
評估觀察者數量對通知效率的影響:
@Test
public void testObserverPerformance() {WeatherData weatherData = new WeatherData();int observerCount = 1000;for (int i = 0; i < observerCount; i++) {weatherData.registerObserver(new SimpleObserver());}long startTime = System.currentTimeMillis();weatherData.setMeasurements(25f, 60, 1013f);long endTime = System.currentTimeMillis();// 驗證耗時是否在合理范圍內Assert.assertTrue(endTime - startTime < 1000); // 1秒內完成
}
優化方向:
- 使用異步通知減少阻塞。
- 限制觀察者數量或引入優先級隊列。
三、常見問題與解決方案
- 內存泄漏:
- 問題:未從主題移除觀察者,導致無法被GC回收。
- 解決方案:在觀察者銷毀時調用
removeObserver()
,或使用弱引用存儲觀察者。
- 循環依賴:
- 問題:主題與觀察者互相依賴,導致棧溢出。
- 解決方案:通過中介者模式解耦。
- 線程安全:
- 問題:多線程環境下注冊/移除觀察者時數據不一致。
- 解決方案:使用
synchronized
或并發集合(如CopyOnWriteArrayList
)。
四、總結
- 實現選擇:優先自定義實現以避免JDK類的缺陷,復雜場景可結合異步通知。
- 測試重點:驗證通知邏輯、線程安全及性能。
- 工具推薦:Mockito用于單元測試,線程池和并發集合用于多線程場景。