設(shè)計模式詳解——觀察者模式

前言
今天我們來看下一個可以有效實現(xiàn)松耦合的設(shè)計模式——觀察者模式,這個設(shè)計模式我之前也僅僅停留在聽說過的層面,關(guān)于它的具體實現(xiàn)更是一知半解,所以今天我們就來簡單剖析下這個設(shè)計模式。
設(shè)計模式
觀察者模式
觀察者模式定義了對象之間的一對多依賴,當一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并自動更新。
要點
觀察者模式定義了對象之間一對多的關(guān)系 主題(可觀察者)用一個共同的接口來更新觀察者 觀察者和可觀察者之間用松耦合方式結(jié)合,可觀察者不知道觀察者的細節(jié),只知道觀察者實現(xiàn)了觀察者接口 使用次模式時,你可以從被觀察者處推或拉數(shù)據(jù)(推的方式被認為是更正確的) 有多個觀察者時,不可以依賴特定的通知次序 java中有多種觀察者模式的實現(xiàn),包括了通用的java.util.Observable,不過需要注意Observable實現(xiàn)上所帶來的問題,有必要的話,可以實現(xiàn)自己的ObservableSwing大量使用觀察者模式,許多GUI框架也是如此觀察者模式還廣泛被應用在許多地方,比如: JavaBeans、RMI
示例
下面我們以一個具體實例,來展示下觀察者模式的具體實現(xiàn)。這里我們就直接以《Head First設(shè)計模式》中的氣象站為例,其中天氣信息就表示被觀察者,天氣布告板就表示訂閱者和觀察者,當天氣發(fā)生變化(被觀察者)時,會通過notifyObserver通知所有觀察者,并調(diào)用他們的控制方法處理數(shù)據(jù)。下面我們就來看下具體實現(xiàn)過程
被觀察者接口接口
這里有三個方法,第一個是注冊觀察者,第二個是移除觀察者,第三個是通知觀察者
public?interface?Subject?{
????void?registerObserver(Observer?observer);
????void?removeObserver(Observer?observer);
????void?notifyObserver();
}
觀察者接口
觀察者接口就一個方法,主要用于更新從被觀察者中獲取到的數(shù)據(jù),該方法會在被注冊者的notifyObserver方法中被調(diào)用
public?interface?Observer?{
????void?update(float?temp,?float?humidity,?float?pressure);
}
控制層接口
這個接口主要是用于處理被觀察者更新的數(shù)據(jù),核心方法就一個。
public?interface?DisplayElement?{
????void?display();
}
被觀察者實現(xiàn)
這里是被觀察者的具體實現(xiàn),被觀察者也可以叫數(shù)據(jù)源,當數(shù)據(jù)發(fā)生變化時,由它負責告知觀察者。
public?class?WeatherData?implements?Subject?{
????private?List?observerList;
????private?float?temp;
????private?float?humidity;
????private?float?pressure;
????public?WeatherData(List?observerList) ?{
????????this.observerList?=?observerList;
????}
????@Override
????public?void?registerObserver(Observer?observer)?{
????????observerList.add(observer);
????}
????@Override
????public?void?removeObserver(Observer?observer)?{
????????observerList.remove(observer);
????}
????@Override
????public?void?notifyObserver()?{
????????observerList.forEach(observer?->?observer.update(temp,?humidity,?pressure));
????}
????public?void?measurementChanged()?{
????????notifyObserver();
????}
????public?void?setMeasurements(float?temp,?float?humidity,?float?pressure)?{
????????this.temp?=?temp;
????????this.humidity?=?humidity;
????????this.pressure?=?pressure;
????????measurementChanged();
????}
}
觀察者實現(xiàn)
觀察者我們實現(xiàn)了兩個,主要是便于后面測試。這兩個實現(xiàn)處理名字不一樣,其他代碼都是一樣的。
public?class?RemoteDisplay?implements?Observer,?DisplayElement?{
????private?Subject?subject;
????private?float?temp;
????private?float?humidity;
????private?float?pressure;
????public?RemoteDisplay(Subject?subject)?{
????????this.subject?=?subject;
????????subject.registerObserver(this);
????}
????@Override
????public?void?display()?{
????????System.out.println(String.format("=======?\nname:%s?\ntemp:%S?\nhumidity:%s?\npressure:%s",?"remote",?temp,?humidity,?pressure));
????}
????@Override
????public?void?update(float?temp,?float?humidity,?float?pressure)?{
????????this.temp?=?temp;
????????this.humidity?=?humidity;
????????this.pressure?=?pressure;
????????display();
????}
}
public?class?CurrentWeatherDataDisplay?implements?Observer,?DisplayElement?{
????private?Subject?subject;
????private?float?temp;
????private?float?humidity;
????private?float?pressure;
????public?CurrentWeatherDataDisplay(Subject?subject)?{
????????this.subject?=?subject;
????????subject.registerObserver(this);
????}
????@Override
????public?void?display()?{
????????System.out.println(String.format("=======?\nname:%s?\ntemp:%S?\nhumidity:%s?\npressure:%s",?"urrentWeatherData",?temp,?humidity,?pressure));
????}
????@Override
????public?void?update(float?temp,?float?humidity,?float?pressure)?{
????????this.temp?=?temp;
????????this.humidity?=?humidity;
????????this.pressure?=?pressure;
????????display();
????}
}
測試代碼
從上面觀察者的實現(xiàn)類中,我們可以看出來,在實例化觀察者的時候,其實已經(jīng)完成了注冊操作,所以這里我們不再需要手動注冊觀察者,后面再被觀察者(數(shù)據(jù))發(fā)生變化時,觀察者會實時被通知。
@Test
????public?void?testWeatherDisplay()?{
????????WeatherData?data?=?new?WeatherData(new?ArrayList<>());
????????new?CurrentWeatherDataDisplay(data);
????????new?RemoteDisplay(data);
????????data.setMeasurements(12,?88,?981);
????????System.out.println("----------------分割線------------------");
????????data.setMeasurements(28,?68,?871);
????????System.out.println("----------------分割線------------------");
????????data.setMeasurements(38,?48,?681);
????}
運作結(jié)果

總結(jié)
從實例的運行結(jié)果來看,觀察者模式特別適用于那些一對多的應用場景,比如數(shù)據(jù)推送同步,當你的平臺數(shù)量發(fā)生變化時,數(shù)據(jù)發(fā)送方只需要將相關(guān)平臺注冊或刪除,即可完成觀察者的變更,可以實現(xiàn)代碼之間的松耦合,提升代碼的擴展性。
與這個設(shè)計模式類似的設(shè)計模式是訂閱者模式,這兩種設(shè)計模式,在我之前的認知中,我覺得他們應該是一樣的,但是今天查閱了相關(guān)資料之后,發(fā)現(xiàn)它們并不完全一樣,這里我們先大概對訂閱者模式有一個基本的認知,后面等我們分享完訂閱者模式之后,我們再來比較這兩個的區(qū)別。
- END -