在正式介紹觀察者模式前鄙陡,我們先引用生活中的小例子來模擬觀察者冕房,先對觀察者模式有一個整體的感覺躏啰。
現(xiàn)實模擬
報紙和雜志的故事。
我們看看報紙和雜志的訂閱是怎么一回事:
- 報紙的任務(wù)就是出版報紙
- 我們向某家報社訂閱報紙耙册,只要他們有新報紙出版给僵,就會給你送來,只要你是他們的訂戶,你就會一直得到新報紙
- 當你們不想再看報紙的時候帝际,向報社取消訂閱蔓同,他們就不會再送報紙來,你也不會再收到報紙
- 只要報社還在運營蹲诀,就會有人向他們訂閱或者取消報紙
這其實就可以理解為是一種觀察者模式斑粱。報社出版者被認為是觀察者模式中的Subject,訂閱報紙的人被認為是觀察者模式中的Observer脯爪。具體的觀察者模式的subject和observer我們后面會介紹则北。
訂閱者通常有很多個,他們訂閱或者取消需要通知出版者痕慢。出版者當報紙有更新時尚揣,就會把新報紙一起推送給訂閱者,所有訂閱者都會收到出版社的所有更新掖举。
再舉個常見的例子快骗,我們常見的手機app,網(wǎng)易新聞或者其他類塔次。只要我們安裝了這個這個應(yīng)用方篮,并在app設(shè)置接收應(yīng)用的消息通知,那么當app有新消息通知時励负,我們就會收到新消息恭取。這里,我們用戶就是觀察者熄守,app就是Subject蜈垮。
觀察者模式定義
觀察者模式是設(shè)計模式中很常用的一個模式。
比較嚴格的解釋是:** 觀察者模式定義了對象之間的一對多的依賴裕照,這樣一來攒发,當一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并自動更新晋南。**
跟圖中的例子一樣惠猿,主題和觀察者定義了一對多的關(guān)系。觀察者依賴于此主題负间,只要主題狀態(tài)一有變化偶妖,觀察者就會被通知。
觀察者模式的類圖可以很好的觀察者模式的設(shè)計思想
觀察者的設(shè)計方式有很多種政溃,但其中實現(xiàn)Subject和observer接口的設(shè)計方式是最常用的趾访、
Subject的接口有三個方法,分別是注冊觀察者董虱,移除觀察者和通知觀察者键兜。對象通過Subject接口注冊成為觀察者,同事也可以通過它從解除觀察者的身份英妓,也就是之前例子中的取消訂閱報紙。
每個Subject通尘栌眩可以有很多個觀察者
具體的Subject對象需要實現(xiàn)Subject接口的三個方法,其中notify方法是用于當狀態(tài)發(fā)生變化時溃槐,來通知觀察者update匣砖,里面一般要調(diào)用觀察者接口的update方法。
所有的觀察者都需要實現(xiàn)Observer接口昏滴,并實現(xiàn)其中的update方法脆粥,以便當主題狀態(tài)發(fā)生變化,觀察者得到主題的通知影涉。用于Subject具體實現(xiàn)類的notify方法的調(diào)用变隔。
具體的Observer都需要繼承至接口,同時他們必須注冊到具體的Subject對象蟹倾,以成為一個觀察者匣缘,并得到更新。
觀察者實現(xiàn)的設(shè)計原則
** 觀察者模式提供了一種對象設(shè)計鲜棠,讓主題和觀察者之間松耦合 **
關(guān)于觀察者的一切肌厨,主題只需要知道觀察者實現(xiàn)了某個接口也就是Observer接口,主題不需要知道觀察者的具體的實現(xiàn)類是誰豁陆,做了些什么或者其他任何細節(jié)柑爸,主題都不需要知道。
任何時候我們都可以增加新的觀察者盒音,因為主題唯一依賴的東西是一個實現(xiàn)Observer接口的對象列表表鳍,所以我們可以隨時增加觀察者。事實上祥诽,在運行時我們可以用新的觀察者取代現(xiàn)有的觀察者譬圣,主題不會受任何影響。同樣的雄坪,也可以在任何時候刪除觀察者厘熟。
當有新的類型的觀察者出現(xiàn)時,主題的代碼不會發(fā)生修改维哈。假如我們有個新的具體類需要當觀察者绳姨,我們不需要為了兼容新類型而修改主題的代碼,所需要的只是在新的類里實現(xiàn)此觀察者的接口阔挠,然后注冊為觀察者即可飘庄。
這里體現(xiàn)了一個設(shè)計原則就是** 為了交互對象之間的松耦合設(shè)計而努力 **
爭取讓對象之間的互相依賴降到最低
代碼實現(xiàn)
我們考慮這樣一個問題:實現(xiàn)一個氣象站監(jiān)測應(yīng)用。
有三個部分谒亦,氣象站(獲取實際氣象數(shù)據(jù)的裝置)竭宰,weatherData對象(追蹤來自氣象站的數(shù)據(jù),并更新布告板)和布告板(顯示目前天氣的狀況給用戶看)
我們要做到的就是建立一個應(yīng)用份招,利用weatherdata對象獲取數(shù)據(jù)切揭,并更新三個布告板。
我們對氣象站的初步設(shè)計圖:
根據(jù)觀察者設(shè)計了一個類圖锁摔,接下來我們實現(xiàn)這個類圖廓旬。從建立接口開始,
package com.liu.itf;
public interface Subject {
public void registerObserver(Observers o);
public void removeObserver(Observers o);
public void notifyObserver();
}
package com.liu.itf;
public interface Observers {
public void update(float temp, float humidity, float pressure);
}
package com.liu.itf;
public interface DisplayElement {
public void display();
}
接下來在weatherdata類中實現(xiàn)Subject接口
package com.liu.model;
import java.util.ArrayList;
import com.liu.itf.Observers;
import com.liu.itf.Subject;
public class WeatherData implements Subject {
private ArrayList<Observers> observers;
private float temperature;
private float pressure;
private float humidity;
public WeatherData() {
// TODO Auto-generated constructor stub
observers = new ArrayList<Observers>();
}
@Override
public void registerObserver(Observers o) {
// TODO Auto-generated method stub
observers.add(o);
}
@Override
public void removeObserver(Observers o) {
// TODO Auto-generated method stub
int i = observers.indexOf(o);
if(i>=0) {
observers.remove(o);
}
}
@Override
public void notifyObserver() {
// TODO Auto-generated method stub
for(int i=0;i<observers.size();i++) {
Observers observer = (Observers)observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObserver();
}
public void setMeasurements(float temperature, float humidity,float pressure) {
this.humidity = humidity;
this.temperature = temperature;
this.pressure = pressure;
measurementsChanged();
}
}
布告板類作為觀察者實現(xiàn)觀察者接口和display接口
package com.liu.view;
import com.liu.itf.DisplayElement;
import com.liu.itf.Observers;
import com.liu.itf.Subject;
public class CurrentConditionDisplay implements DisplayElement, Observers {
private float temperature;
private float pressure;
private float humidity;
private Subject weatherData;
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
// TODO Auto-generated method stub
this.temperature = temp;
this.humidity = humidity;
display();
}
@Override
public void display() {
// TODO Auto-generated method stub
System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
}
}
編寫一個測試類:
import com.liu.model.WeatherData;
import com.liu.view.CurrentConditionDisplay;
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
weatherData.setMeasurements(80, 45, 30.4f);
}
}
小結(jié)
- 觀察者定義了對象之間一對多的關(guān)系谐腰。
- 主題用一個共同的接口來更新觀察者
- 觀察者和主題之間用松耦合的方式連接孕豹,主題不知道觀察者的細節(jié),只知道觀察者實現(xiàn)了觀察者接口