什么是觀察者模式?
定義了對象之間的一對多依賴,這樣一來,當(dāng)一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并自動更新。觀察者模式定義了一系列對象之間的一對多關(guān)系。
案例(氣象站)
氣象站建立在WeatherData對象上,由WeatherData對象負(fù)責(zé)追蹤目前的天氣狀況(溫度、溫度、氣壓)。建立三種布告板,分別顯示目前的狀況、氣象統(tǒng)計以及簡單的預(yù)報。當(dāng)WeatherData獲得到新的測量數(shù)據(jù)時候,三種布告板必須實(shí)時更新。

WeatherData從氣象站追蹤溫度,濕度,氣壓這一部分工作不需要我們考慮。
我們只需要建立WeatherData和布告板們之間的關(guān)系。
首先先來看看WeatherData準(zhǔn)備了什么?
public class WeatherData {
private float temperature;
private float humidity;
private float pressure;
//get方法獲取數(shù)據(jù)
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
//當(dāng)氣象測量數(shù)據(jù)更新的時候,此方法會被調(diào)用
public void measurementsChanged() {
}
}
在使用觀察者模式解決上述案例之前,先來了解一下觀察者模式。
舉個例子(??)
報紙的訂閱。

報社定期收集事件并出版,報紙會送給訂閱該報社的用戶們??梢杂行碌挠脩粲嗛唸笊?,用戶也可以取消訂閱。取消訂閱后,報社有新報發(fā)版的時候?qū)⒉粫托聢蠹垇怼?/p>
出版者(主題)+訂閱者們(觀察者們)=觀察者模式
當(dāng)主題發(fā)生改變的時候,就會通知該主題的觀察者數(shù)據(jù)已經(jīng)更新。
再舉個例子(??)
博主和粉絲的關(guān)系。

從這個案例分析,博主就是主題,而粉絲就是觀察者們,當(dāng)博主發(fā)微博后,只要關(guān)注,也就是訂閱他的粉絲們都會收到該博主的更新的博文??梢孕略龇劢z,老粉也可以取關(guān)。從這個案例也可以看出,觀察者模式重點(diǎn)是主題和觀察者們之前的關(guān)系,主題是怎樣獲取數(shù)據(jù),觀察者模式并不關(guān)心。其中新粉和老粉取關(guān)不會影響到博主,這一現(xiàn)象也正是松耦合的威力。主題不需要知道觀察者的具體類是誰,做了什么事情。主題唯一以來的東西是一個實(shí)現(xiàn)Observer接口的對象列表。改變主題或者觀察者其中一方,并不會影響另一方,因?yàn)閮烧呤撬神詈稀?/p>
進(jìn)階:通過微博博主和粉絲的案例。這個博主肯定也會關(guān)注其他的博主,而粉絲們當(dāng)然也會被其他人關(guān)注著。這也就是說一個對象可以有兩張身份,既可以是主題,也可以是觀察者。但要注意在一個觀察者模式中,主題和觀察者模式是一對多的關(guān)系。
既然搞清楚了觀察者的概念,現(xiàn)在就用它來實(shí)現(xiàn)一開始的氣象站案例吧。
設(shè)計氣象站

定義Subject 接口
public interface Subject {
//注冊一個觀察者
public void registerObserver(Observer o);
//移除一個觀察者
public void removeObserver(Observer o);
//當(dāng)主題狀態(tài)改變時,該方法被調(diào)用,以通知所有的觀察者
public void notifyObserver();
}
定義Observer接口
public interface Observer {
//所有的觀察者都必須實(shí)現(xiàn)該方法;當(dāng)氣象觀測值改變時,主題會把這些狀態(tài)值當(dāng)作方法的參數(shù)
public void update(float temp, float humidity, float pressure);
}
定義該案例特有的顯示接口
public interface DisplayElement {
//當(dāng)布告板需要顯示時,調(diào)用該方法
public void display();
}
實(shí)現(xiàn)WeatherData
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(o);
}
}
@Override
public void notifyObserver() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
//當(dāng)主題狀態(tài)改變時,該方法被調(diào)用,以通知所有的觀察者
public void measurementsChanged() {
notifyObserver();
}
//主題的狀態(tài)改變,數(shù)值都變
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
建立CurrentConditionDisplay布告板(省略另外兩個布告板)
//目前狀況布告板 是觀察者,也要展示
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
//在構(gòu)造器中要定義這個觀察者依賴的主題,還要在這個主題下面注冊自己
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions :" + temperature + "F degrees and " + humidity + "% humidity");
}
//更新變量并且實(shí)時暫display
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
測試
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(20f, 30f, 40f);
}
}
測試結(jié)果
Current conditions :20.0F degrees and 30.0% humidity
Statistics conditions :20.0F degrees and 30.0% humidity40.0壓力
Forecast conditions :20.0F degrees and 30.0% humidity40.0壓力
上訴案例看起來很完美,但其實(shí)也有點(diǎn)小問題。
問題一:Observer接口的參數(shù)定死了,這樣一點(diǎn)都不彈性,以后要加入其它的變量怎么辦。
問題二:當(dāng)主題改變時,會一股腦將數(shù)據(jù)都給觀察者,但其實(shí)觀察者不需要這么多數(shù)據(jù),它想要的只是主題中的一部分?jǐn)?shù)據(jù)。這種情況獲取,可以由觀察者去獲取自己想要的數(shù)據(jù)。
恰巧Java內(nèi)置的觀察者模式可以解決這兩個問題。是的,你沒聽錯,Java內(nèi)置了觀察者,這就說明觀察者這種模式很常用了吧。下面用Java自帶的觀察者模式解決下氣象站的案例。

Java內(nèi)置的主題:java.util.Observable
Java內(nèi)置的觀察者:java.util.Observer
使用Java內(nèi)置的主題:
public class WeatherData extends Observable{
private float temperature;
private float humidity;
private float pressure;
//不需要觀察者的list的變量,也不需要再構(gòu)造器中建立觀察者的list,因?yàn)镺bservable已經(jīng)幫我們完成了
public WeatherData() {
}
public void measurementsChanged(){
//再調(diào)用通知觀察者們之前,先調(diào)用setChanged()表明狀態(tài)已經(jīng)變了。
setChanged();
notifyObservers();
}
public void setMeasurements(float temperature,float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
使用Java內(nèi)置的觀察者
public class CurrentConditionDisplay implements Observer,DisplayElement {
private float temperature;
private float humidity;
private Observable weatherData;
public CurrentConditionDisplay(Observable weatherData) {
this.weatherData = weatherData;
weatherData.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData)o;
//觀察者主動去主題拉數(shù)據(jù)
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
@Override
public void display() {
System.out.println("Current conditions :"+ temperature +"F degrees and " + humidity +"% humidity");
}
}
測試
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setMeasurements(20,30,40);
}
}
測試結(jié)果,注意順序。
Statistics conditions :20.0F degrees and 30.0% humidity40.0壓力
Forecast conditions :20.0F degrees and 30.0% humidity40.0壓力
Current conditions :20.0F degrees and 30.0% humidity
順序不一致,所以不要依賴觀察者被通知的順序。為什么會這樣呢?查看源碼可知
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
//其實(shí)是這里的遍歷搞得鬼
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
詳解Observable
setChanged()方法是不是多此一舉?
當(dāng)然不是,比如氣象站的例子,氣候肯定是時時刻刻在變化,如果我們的硬件很靈敏,那么WeatherData也會跟著氣候一直跟著改變,那布告板不也實(shí)時更新。這樣是不是太多平凡了?所以setChanged()起到了作用,當(dāng)setChanged調(diào)用時候,才主題才會去通知觀察者數(shù)據(jù)改變了,而不是時時刻刻通知了。
Observable居然是一個類不是一個接口?
是的,Observable是一個類。這樣就限制了它的復(fù)用。只能繼承,而Java不支持多重繼承。Observable中也沒有接口,無法建立用戶的實(shí)現(xiàn)。但其實(shí)你也可以自己實(shí)現(xiàn)自己的Observable接口。
白話總結(jié)
一個主題掌握著數(shù)據(jù),一群觀察者想要獲得主題的數(shù)據(jù),于是這些觀察者就訂閱了這個主題,訂閱成功之后,當(dāng)主題掌握的數(shù)據(jù)改變了,那就會通知訂閱自己的觀察者數(shù)據(jù)改變了。觀察者有有種獲得數(shù)據(jù)的方式,一種是主題把所有數(shù)據(jù)都推給觀察者,另一張是觀察者自己去拉自己想要的數(shù)據(jù)。主題和觀察者可自定義也可以用Java中內(nèi)置的。