-
定義:觀察者模式定義了一系列一對多的關系,當一個對象改變狀態(tài)韩脑,其他依賴者都會收到通知直撤。
從類圖上看其實就是一個Subject(主題)依賴多個Observer(觀察者)哎榴,一個Observer依賴一個Subject寝杖。一對多的關系违施。Subject定義了觀察者注冊和注銷的方。以及主題改變時通知在線觀察者的方法朝墩。Observer定義了當主題改變式醉拓,被通知時要調用的update方法。關鍵在這個依賴收苏。當Observer被實例化的時候亿卤,會告訴調用Subject的registerObserver()注冊到Subject。當Subject更新的時候會最終調用Observer的update方法通知到觀察者鹿霸。
松耦合設計原則
:連個對象之間要交互排吴,暴露的東西越少越好。只需要知道對方能干嘛懦鼠,而不需要知道具體怎么實現(xiàn)的钻哩。本例中。Observer不需要知道Subject怎么通知我的肛冶,只需要知道街氢,通知我update方法被調用了即可。反過來睦袖。Subject不需要知道Observer具體是誰珊肃,做了什么。只需要知道它是個Observer即可馅笙。
案例:氣象站的設計
需求: 目前已經(jīng)有WeatherData類伦乔。里面有獲取溫度、濕度董习、氣壓的方法烈和。當新的數(shù)據(jù)備妥時measurementsChanged方法會被調用。現(xiàn)在需要增加三個使用天氣數(shù)據(jù)的的布告板皿淋,分別是“目前狀況”布告招刹、“天氣預報”布告、“氣象統(tǒng)計”布告窝趣。并且要求這些布告是可以拓展的疯暑,也就是可以刪除也可以增加其他布告板。
錯誤的代碼設計:面向過程(直接懟代碼)
既然知道了當數(shù)據(jù)更新的時候measurementsChanged方法會被調用高帖,那直接在上面寫代碼就可以實現(xiàn)需求了。于是寫出了類似下面的代碼畦粮。簡稱“直接懟”散址。這就很不友好了乖阵。這樣的后果就是日后如果氣象站有所變更,我需要直接修改代碼预麸。在公共的代碼區(qū)域直接修改代碼會造成意想不到的結果瞪浸。比如所有被通知的布告板,出現(xiàn)了不該出現(xiàn)的數(shù)據(jù)吏祸。
public class WeatherData {
public String getTemperature(){
return "溫度";
}
public String getHumidity(){
return "濕度";
}
public String getPressure(){
return "氣壓";
}
/**
* 新的測量數(shù)據(jù)備好后會被調用
*/
public void measurementssChanged(){
String temperature = getTemperature();
String humidity = getHumidity();
String pressure = getPressure();
//目前
currentConditionsDisplay.update(temperature,humidity,pressure);
//統(tǒng)計
statisticsDisplay.update(temperature, humidity, pressure);
//預報
forecastDisplay.update(temperature, humidity, pressure);
}
}
使用觀察者模式
思考:該需求很適合使用觀察者模式对蒲,氣象站需要給布告板數(shù)據(jù),而布告板展示數(shù)據(jù)贡翘。這不就是觀察者模式的經(jīng)典嗎蹈矮。
1、定義被觀察者接口:主題
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 9:10.
*
* 主題接口:對象使用此接口注冊為觀察者鸣驱》耗瘢或者把自己是刪除。
*/
public interface Subject {
/**
* 注冊為觀察者
*/
void registerObserver(Observer observer);
/**
* 從觀察者列表里刪除(注銷)
*/
void removeObserver(Observer observer);
/**
* 通知當前所有觀察者踊东,主題狀態(tài)改變
*/
void notifyObserver();
}
2北滥、定義觀察者接口
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 9:14.
*
* 觀察者接口:所有潛在的觀察者必須實現(xiàn)觀察者接口,該demo中只有update一個方法闸翅,當Subject狀態(tài)改變時,它被調用
*/
public interface Observer {
void udpate(float temp, float humidity,float pressure);
}
3、定義業(yè)務功能接口
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 9:31.
*/
public interface DisplayElement {
/**
* 布告板展示時調用此方法
*/
void display();
}
4流部、實現(xiàn)具體主題
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/14 16:48.
*/
public class WeatherData implements Subject{
//依賴觀察者:subject包含多個Observer
private ArrayList<Observer> observers;
//溫度
private float temperature;
//濕度
private float humidity;
//氣壓
private float pressure;
public WeatherData() {
this.observers = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if (observers.indexOf(observer) >= 0) {
observers.remove(observer);
}
}
@Override
public void notifyObserver() {
for (Observer observer : observers) {
observer.udpate(temperature,humidity,pressure);
}
}
/**
* 新的測量數(shù)據(jù)備好后會被調用
*/
public void measurementsChanged(){
notifyObserver();
}
public void setMeasurements(float temperature,float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
5导狡、實現(xiàn)具體觀察者
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 9:32.
*/
public class CurrentConditionDisplay implements Observer, DisplayElement {
//依賴具體被觀察者(主題)
private Subject weatherData;
private float temperature;
private float humidity;
private float pressure;
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
//注冊
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("CurrentCondition:");
System.out.println("temperature = " + temperature);
System.out.println("humidity = " + humidity);
System.out.println("pressure = " + pressure);
}
@Override
public void udpate(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 9:32.
*/
public class ForecastDisplay implements Observer, DisplayElement {
private Subject weatherData;
private float temperature;
private float humidity;
private float pressure;
public ForecastDisplay(Subject weatherData) {
this.weatherData = weatherData;
//注冊
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Forecast:");
System.out.println("temperature = " + temperature);
System.out.println("humidity = " + humidity);
System.out.println("pressure = " + pressure);
}
@Override
public void udpate(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 9:32.
*/
public class StatisticDisplay implements Observer, DisplayElement {
private Subject weatherData;
private float temperature;
private float humidity;
private float pressure;
public StatisticDisplay(Subject weatherData) {
this.weatherData = weatherData;
//注冊
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Statistic:");
System.out.println("temperature = " + temperature);
System.out.println("humidity = " + humidity);
System.out.println("pressure = " + pressure);
}
@Override
public void udpate(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
6、測試
public class Test {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
StatisticDisplay statisticDisplay = new StatisticDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(12,12,12);
weatherData.setMeasurements(13,13,13);
}
}
總結
思考:
觀察者模式的核心思想小編理解是松耦合的實現(xiàn)思想遗菠。相互依賴联喘,僅僅暴露需要傳輸?shù)牟糠郑渌[藏在各自的區(qū)域中辙纬。相互只知道相互需要知道的東西豁遭,一點不多一點不少。利用組合的思想贺拣。面向接口編程蓖谢。方便拓展并且實現(xiàn)了解耦,主題和觀察者解耦譬涡,業(yè)務和觀察者解耦闪幽。代碼是相互獨立的,使用是相互依賴的涡匀。
缺點:
本例后盯腌,當我們嘗試去拓展一個新的Subject的時候,我們會發(fā)現(xiàn)我們寫了重復的代碼陨瘩。注冊刪除和通知Observer這些方法在每一個實現(xiàn)類當中都需要去實現(xiàn)腕够。關于這個级乍,使用jdk8的小伙伴們說很容易解決的啦。jdk8支持接口方法的默認實現(xiàn)嘛帚湘,子類可以不實現(xiàn)這些方法玫荣。 比如這樣。
public interface Subject {
ArrayList<Observer> observers = new ArrayList<Observer>();
/**
* 注冊為觀察者
*/
default void registerObserver(Observer observer){
observers.add(observer);
}
/**
* 從觀察者列表里刪除(注銷)
*/
default void removeObserver(Observer observer){
if (observers.indexOf(observer) >= 0) {
observers.remove(observer);
}
}
/**
* 通知當前所有觀察者大诸,主題狀態(tài)改變
*/
void notifyObserver();
}
public class WeatherData implements Subject {
private float temperature;
private float humidity;
private float pressure;
@Override
public void notifyObserver() {
for (Observer observer : observers) {
observer.udpate(temperature, humidity, pressure);
}
}
/**
* 新的測量數(shù)據(jù)備好后會被調用
*/
public void measurementsChanged() {
notifyObserver();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
JDK內置觀察者模式
其實在java.util下已經(jīng)內置好咯額觀察者模式供我們使用捅厂。只不過不同于上面的案例。我們可以看一下具體區(qū)別在哪资柔。
JDK定義Observable類為被觀察者焙贷,對,沒看錯建邓,這個是類盈厘,比起之前我們自己定義的Subject,多了一個狀態(tài)changed屬性官边,調用setChanged方法設置為true沸手,調用notifyObservers方法的時候設置為false,changed的設計可以讓我們更有效的控制變化的通知頻率注簿,更加靈活契吉。觀察者是接口Observer。update方法接受一個具體被觀察者以及Object類型的參數(shù)诡渴。接受的參數(shù)就包含了依賴的被觀察者捐晶,第二個參數(shù)方便我們根據(jù)業(yè)務傳入其他的參數(shù)。
使用JDK內置觀察者模式改造氣象站
import java.util.Observable;
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
/**
* 新的測量數(shù)據(jù)備好后會被調用
*/
public void measurementsChanged() {
setChanged();
notifyObservers();
}
public float getPressure() {
return pressure;
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
}
import com.example.observer.demo5.DisplayElement;
import com.example.observer.demo5.subject.WeatherData;
import java.util.Observable;
import java.util.Observer;
/**
* Project <demo-project>
* Created by jorgezhong on 2018/9/18 9:32.
*/
public class CurrentConditionDisplay implements Observer, DisplayElement {
private Observable observable;
private float temperature;
private float humidity;
private float pressure;
public CurrentConditionDisplay(Observable observable) {
this.observable = observable;
//注冊
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("CurrentCondition:");
System.out.println("temperature = " + temperature);
System.out.println("humidity = " + humidity);
System.out.println("pressure = " + pressure);
}
@Override
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData) observable;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
this.pressure = weatherData.getPressure();
display();
}
}
}
案例總結:
JDK內置的觀察者模式考慮的會多一點妄辩,比如changed的設置惑灵。比如Observer接收Observable和參數(shù)Object會更加適合實際應用。但是由于被觀察者使用的是繼承眼耀,我們的設計原則是多用組合少用繼承英支。既然可以用接口改造那就用接口改造好了。jdk8也支持接口方法的默認實現(xiàn)哮伟。還有一點區(qū)別干花,用jdk的實現(xiàn)是無法保證通知順序的,因此不要選擇依賴通知的順序楞黄。
設計原則:
- 松耦合:所有的設計都盡量松耦合池凄。設計為此而努力
- 開閉原則:對拓展開放,對修改關閉鬼廓。觀察者模式中我們可以通過拓展被觀察者而實現(xiàn)拓展肿仑,不對舊的代碼做出修改符合開閉原則。
-重點設計:我們發(fā)現(xiàn)使用了設計模式會大量使用接口抽象方法。抽象多了尤慰,層次就會增多勾邦。同樣的,遵循開閉原則的代碼一般都會引入新的抽象層次割择,增加代碼的復雜性,太復雜的代碼往往都不好理解萎河,失去了其意義荔泳。因此對重點進行設計,避免過度設計虐杯。