氣象檢測應用
我們接到一個任務,現在有一個氣象站,氣象站採集的數據在一個WeatherData
對象中,我們的任務是利用WeatherData
對象獲取數據,並更新三個佈告板:目前情況、氣象統計和天氣預報
簡單的WeatherData對象
簡單的WeatherData
對象應該是這個樣子的
class WeatherData{
private float temperature; // 溫度
private float humidity; // 溼度
private float pressure; //氣壓
// 獲取溫度 溼度 氣壓
public float getTemperature() {
// 方法體
};
public float getHumidity() {
// 方法體
};
public float getPressure() {
// 方法體
};
// 數據發生變化時,獲取最新數據並更新佈告板
public void measurementsChanged() {
temperature = getTemperature();
humidity = getHumidity();
pressure = getPressure();
// 三個佈告板分別更新
Board1.update(temperature, humidity, pressure);
Board2.update(temperature, humidity, pressure);
Board3.update(temperature, humidity, pressure);
}
}
可是我們看到:三個佈告板是分別更新的,而且參數固定,如果以後有新的佈告板需要添加、或者每個佈告板顯示的數據需要添加,代碼維護起來就會很麻煩
// 這一段
// 三個佈告板分別更新 參數(每個佈告板要顯示的數據)固定
Board1.update(temperature, humidity, pressure);
Board2.update(temperature, humidity, pressure);
Board3.update(temperature, humidity, pressure);
在設計模式-策略模式中,我們提到過這樣的設計原則:找出應用中可能需要變化之處,把他們獨立出來,不要和那些不需要變化的代碼混在一起
根據這個原則,在WeatherData
對象中,佈告板一定是變化的,所以要把它單獨拿出來
觀察者模式
在修改上面的代碼之前,我們先了解一下什麼是觀察者模式
對於報紙出版商和訂閱者之間
- 只要你訂閱了報紙,你就是訂閱者
- 出版商有了新的新聞就會給你以及其他所有訂閱者發送報紙
- 你不用時時刻刻關注出版商有什麼新聞變化,因爲如果有新聞變化出版商會自動給你派發報紙
- 如果你不想看報紙,就取消訂閱
這種形式就是觀察者模式,在觀察者模式中,出版商叫做“主題”(Subject
),訂閱者叫做“觀察者”(Oberver
)
觀察者無需時時刻刻監視主題的變化,主題發生變化時會自動通知觀察者
觀察者模式:定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新
實現觀察者模式的方法不止一種,但是以包含SUbject
和Observer
接口的類設計的做法最常見
我們來看一下
設計氣象站
我們將氣象監測系統分爲兩類,第一類就是WeatherData
對象所代表的Subject
,第二類就是所有佈告板Board1,2,3……
所代表的Observer
首先設計Subject
接口,需要的功能:註冊和刪除觀察者,將新信息推送給所有觀察者
public interface Subject{
// 註冊觀察者
public void registerObserver(Observer o);
// 移除觀察者
public void removeObserver(Observer o);
// 通知所有佈告板
public void notifyObservers();
}
Observer
接口只要實現更新就可以了
public interface Observer{
// 數據發生變化時,更新自身的值更新自身的值
public void update(float temperature, float humidity, float pressure);
}
現在我們的WeatherDate
類實現Subject
接口
class WeatherData implements Subject{
private float temperature; // 溫度
private float humidity; // 溼度
private float pressure; //氣壓
private ArrayList observers; //記錄所有註冊WeatherDate的觀察者,在構造函數中初始化
public WeatherData(){
observers = new ArrayList();// 初始化ArrayList
}
//註冊新的觀察者
public void registerObserver(Observer o){
observers.add(o);
}
// 移除觀察者
public void removeObserver(Observer o){
// 查找觀察者是否已經訂閱,如果訂閱了返回位置,未訂閱返回-1
int i = observers.indexOf(o);
if( i>= 0)
observers.remove(o);
}
// 通知每個觀察者更新自己
public void notifyObservers() {
for(int i=0; i<observers.size(); i++) {
Observer observer = (Observer)observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
// 數據發生改變,調用通知觀察者的方法
public void measurementsChanged()() {
notifyObservers();
}
// 設置新數據
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
// 數據發生了變化
measurementsChanged();
}
}
每個佈告板都實現Observer
接口,我們就以Board2
爲例
public class Board2 implements Observer{
private float temperature; // 溫度
private float humidity; // 溼度
private float pressure; //氣壓
private Subject weatherData // 記錄訂閱的是哪個Subject
public Board2 (Subject weatherData){
// 訂閱WeatherData
this.weatherData = weatherData;
// 將自己添加到WeatherData的觀察者列表中
weatherData.registerObserver(this);
}
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
//顯示數據
display();
}
// 顯示數據
public void display() {
System.out.println(temperature + " "
+ humidity + " "
+ pressure);
}
}
- 目前來說,當值變化時(
update()
)調用display()
時很合理的,然而還有很多更好的方法設計顯示數據的方式,如MVC設計模式
Subject
的引用其實沒必要保存,因爲構造完Board2
之後就用不到了,但是以後我們可能想要取消註釋,如果已經有了對Subject
的引用會比較方便
到目前爲止我們已經從無到有的完成了觀察者模式
使用Java內置的觀察者模式
JavaAPI有內置的觀察者模式,java.util
包中包含最基本的Observer
接口和Observerable
類,Observable
相當於Subject
,注意一個是類,一個是接口
註冊觀察者
只要一個類實現了Observaer
接口,那麼就可以調用任何Observable
對象的addObserver()方法
,把自身註冊到該Observalbe
上,不想觀察時可以調用Observable
對象的deleteObserver()
方法
主題(Subject、Observable)發出通知
定義一個類擴展java.util.Observable
類,這之後
- 首先調用
setChanged()
方法,標記爲“當前主題的數據已經發生了變化” - 然後調用
notifyObservers
方法中的任意一個:notifyObservers()
,或notifyObservers(Object arg)
,後者可以傳遞任何類型的數據給觀察者
爲什麼設置setChanged()
如果setChanged()
沒有設置,那麼調用notifyObservers
就不會通知觀察者,調用notifyObservers
之後,又會回到沒有設置setChanged()
的狀態
這樣的好處是可以自定義通知頻度,比如氣象觀察站的溫度都是0.01
這樣變化的,而且變化極快,每次變化都會通知觀察者
可是佈告板上不需要這麼精細,只需要每1
度變化就可以了,那麼我們就可以每當有整整1度的溫度發生變化時再設置setChanged()
讓它通知觀察者
觀察者接收通知
觀察者有update(Observable o, Object arg)
方法,第一個參數表示是哪個主題通知它的,第二個參數是主題調用其自身的notifyObserves
時傳遞的參數
關於主題推送變化和觀察者拉取變化
主題的信息發生變化時,其實這個變化不是主題“推”給觀察者的,而是主題通知觀察者,讓觀察者主動"拉"下來的
Observable
源碼大意是這樣的
nottifyObservers(Object arg){
if(設置了setChanged){
for( 觀察者列表中的每一個觀察者 ){
update( this, arg );
}
將類設置爲 未設置setChanged 之前的狀態
}
}
nottifyObservers(){
nottifyObservers( null );
}
程序不要依賴於通知順序
當Observable
的數據發生變化時,Observer
被通知到的順序是不一定的,所以你的程序千萬不要建立在某一個特定通知順序的上
java.util.Observable的黑暗面
java.util.Observable
是個類!嘿你說氣不氣,甚至它一個接口都沒實現,也就是說,java.util.Observable
的的實現會有很多問題,限制了它的使用和複用
- 如果一個類相同時繼承超類和
java.util.Observable
就不可能了 - 沒有
Observable
接口,也就說明了你不能創造一個實現Observable
的類來和java內置的Observable API
搭配使用 java.util.Observable
類中,setChanged()
方法是project
的,這也就意味着:除非你的類繼承自Observable
類,否則無法創造Observable
實例組合到自己的對象中,這違背了我們在設計模式-策略模式提到過的設計原則:“多用組合,少用繼承”
觀察者模式遵循設計原則
-
設計原則:找出程序中會變化的方面,然後將其和固定不變的方面分離
– 觀察者模式中,會改變的是主題的狀態,以及觀察者的數目和類型。用這個模式,你可以改變依賴於主題狀態的對象,卻不必改變主題,這就叫提前規則 -
設計原則:針對接口編程,不針對實現編程
– 主題與觀察者都使用接口,觀察者利用主體的接口向主題註冊,主題利用觀察者接口通知觀察者,這樣可以讓兩者之間運作正常,又同時鬆耦合的優點 -
設計原則:多用組合,少用繼承
– 觀察者模式利用“組合”將許多觀察者組合進主題中。對象之間的這種關係不是通過繼承產生的,不是在運行時利用組合的方式產生的
參考:《Head First 設計模式》