Observer Pattern(觀察者模式)定義:
在物件之間定義一對多的依賴,這樣一來,當一個物件改變狀態,依賴它的物件都會收到通知,並自動更新。
幹說定義肯定沒有舉例理解的透徹。想到Observer Pattern(觀察者模式)就來舉個生活中的例子來幫助我們更好消化和理解其具體含義。
舉例:
訂閱雜誌或者報紙,這裡面有兩個主角,一個是報紙雜誌的供應商(報社),一個是報紙雜誌的訂閱者。這裡就是被觀察者也叫主題(供應商)和觀察者(訂閱者)。主題(Subject)應該有觀察者名單,當主題有新的報紙售出時將按主體持有的觀察者名單一個一個傳送新報紙(傳送沒有先後順序,一切按儲存順序傳送)。
同時主題還應該有三個方法:
一、將觀察者寫入名單中(registerObserver())
二、將觀察者從名單中刪除(removeObserver())
三、當有新訊息傳送及時通知名單中所有觀察者(notifyObservers())
public interface Subject {//主題介面,所有報社都要實現該介面 /*沒有儲存訂閱者的列表,是因為我們不想在介面中寫死儲存方式,
讓程式設計人員自己在實現介面的時候寫入想要的儲存方式(如:連結串列,陣列,棧,佇列等)
這樣更合理。*/ public void registerObserver(Observer o);//將訂閱者登記在列表中
public void removeObserver(Observer o);//將訂閱者從列表中移除
public void notifyObservers(Object arg);//有參通知方法,有新的訊息即使通知列表中所有訂閱者
public void notifyObservers();//無參通知方法
}
Subject
觀察者所具有的東西就會少一些:
首先,內部需要有儲存主題的物件,這樣知道觀察者所訂閱的報社是哪一家,具有主題物件還有一個重要的原因,把登記、刪除、通知觀察者的功能全部委託給主題去做。
其次,還需要有更新自己訊息的方法(update())新的訊息傳送過來,觀察者也要及時更新自己內部訊息,將舊的訊息替換成新的訊息。
public interface Observer{//所有訂閱者要實現的介面
/*在介面中,沒有主題物件,也是因為不想將主題物件寫死在介面中,
在具體類中寫入更好*/ public void update(Subject sub, Object args);//接收到新訊息,及時更新
}
Observer
現在,我們來以具體的生活例子來介紹如何實現觀察者模式(Observer Pattern):
有一家氣象站,氣象站本身已經具有WeatherData物件(相當於報社功能,可以獲得目前的溫度、溼度、氣壓三種資料)。
我們需要編寫一個應用,該應用有很多種顯示模式(從溫度、溼度、氣壓中任選一到三個組合就是一種模式)。
當WeatherData物件獲得最新的測量資料時,我們的應用可以及時更新顯示模式中的資料。
根據要求寫程式:
主題介面:
public interface Subject {//主題介面,所有報社都要實現該介面 /*沒有儲存訂閱者的列表,是因為我們不想在介面中寫死儲存方式,
讓程式設計人員自己在實現介面的時候寫入想要的儲存方式(如:連結串列,陣列,棧,佇列等)
這樣更合理。*/ public void registerObserver(Observer o);//將訂閱者登記在列表中
public void removeObserver(Observer o);//將訂閱者從列表中移除
public void notifyObservers(Object arg);//有參通知方法,有新的訊息即使通知列表中所有訂閱者
public void notifyObservers();//無參通知方法
}
Subject
主題介面實體類:
import java.util.ArrayList; public class WeatherData implements Subject{//氣象站的實現類
private ArrayList observers;//觀察者列表
private float temperature;//資料之一:溫度
private float humidity;//資料之二:溼度
private float pressure;//資料之三:氣壓
private boolean status;//資料是否更新的標誌 public WeatherData(){//初始化時,為觀察者列表賦值
observers = new ArrayList();
} public float getTemperature(){//獲取溫度的方法
return this.temperature;
} public float getHumidity(){//獲取溼度的方法
return this.humidity;
} public float getPressure(){//獲取氣壓的方法
return this.pressure;
} public void registerObserver(Observer o){//將觀察者記錄在列表中
observers.add(o);
} public void removeObserver(Observer o){//將觀察者從列表中刪除
int i = observers.indexOf(o);
observers.remove(i);
} public void notifyObservers(Object args){//有參通知觀察者方法
if(status){//判斷資料是否有更新
for(int i = 0; i < observers.size(); i++){
Observer observer = (Observer) observers.get(i);
observer.update(this, args);
}
status = false;//訊息傳送成功後,將更新標誌位重置
}
} public void notifyObservers(){//無參通知觀察者方法
notifyObservers(null);
} public void setChange(){//資料是否更新的標誌
status = true;
} public void setMeasurements(float temperature, float humidity, float pressure){//資料更新方法
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
} public void measurementsChanged(){//資料改變後呼叫該方法
setChange();
notifyObservers();
} }
WeatherData
觀察者介面:
public interface Observer{//所有訂閱者要實現的介面
/*在介面中,沒有主題物件,也是因為不想將主題物件寫死在介面中,
在具體類中寫入更好*/ public void update(Subject sub, Object args);//接收到新訊息,及時更新
}
Observer
顯示更新資料的介面:
public interface DisplayElement{//顯示更新資料的介面
public void display();
}
DisplayElement
觀察者介面實體類:
public class CurrentConditionsDisplay implements Observer, DisplayElement{//顯示當前溫度、溼度的類
private WeatherData weatherData;//定義訂閱的主題物件
private float temperature;//溫度資料
private float humidity;//溼度資料 public CurrentConditionsDisplay(WeatherData weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);//將該觀察者物件登記在主題的觀察者列表中
} public void update(Subject sub, Object args){//資料更新方法
if(sub instanceof WeatherData){
WeatherData weatherData = (WeatherData) sub;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
} public void display(){//顯示資料的方法
System.out.println("Current conditions:" + temperature + "F degrees and " + humidity + "%humidity");
}
}
CurrentConditionsDisplay
測試類:
public class WeatherStation{
public static void main(String[] agrs){
WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); weatherData.setMeasurements(80, 65, 30.4f);//更新資料
weatherData.setMeasurements(82, 70, 29.2f);//更新資料
weatherData.setMeasurements(79, 90, 29.2f);//更新資料
}
}
WeatherStation
編譯執行結果:
上面程式碼已經很完善了,而且不知道你有沒有發現,其實每次WeatherData更新資料都是把所有資料都更新,但是我們的CurrentConditionsDispaly只獲取溫度和溼度兩個資料,並且從來不獲取多餘的氣壓資料。這就是資料推送(Push)和資料抽取(Pull)的區別。
Push:不管你有沒有訂閱該資料,主題都會將該資料傳送給訂閱者,再由訂閱者決定資料的取捨,沒用的資料就不會記錄在自己的資料中。
Pull:訂閱者想要什麼資料由訂閱者說了算,主題只需提供獲取資料的方法(get...())就好,而上面我們的氣象站就是使用了資料抽取方式。
思想提煉:
1.多用組合,少用繼承
2.為互動物件之間的鬆耦合設計而努力