Head First設計模式讀書筆記二 觀察者模式

Head First設計模式讀書筆記二 觀察者模式

本文示例程式碼材料源自Head First設計模式
以前整理自己整理的策略模式的連結:
https://blog.csdn.net/u011109881/article/details/59773041

思想

觀察者模式是使用的比較普遍的設計模式,其核心思想是在被觀察者(Observable/Subject)中放入觀察者(Observer)的例項列表,一旦被觀察者有資料更新,則遍歷觀察者列表,呼叫觀察者方法來更新資料。

下面舉個例子:比如一家氣象站有氣象資料,擁有歷史 當前和未來數天的天氣資料,現在要將這些資料顯示到天氣佈告板上去,但是,佈告板有詳細的佈告板也有簡單的佈告板,總之,資料可能給佈告板的都是相同,但是佈告板可能顯示成不同的樣子,或者佈告板只取它需要的資料顯示。這個例子可以用觀察者模式來實現。

示例思路(規劃類圖)

這裡寫圖片描述
UML結構圖
這裡寫圖片描述

實際程式碼

Bean

public class WeatherBean {
double temperature;
double humidity;
double pressure;
public double getTemperature() {
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
public double getHumidity() {
return humidity;
}
public void setHumidity(double humidity) {
this.humidity = humidity;
}
public double getPressure() {
return pressure;
}
public void setPressure(double pressure) {
this.pressure = pressure;
}
}

觀察者的介面

public interface DisplayElement {
public void display(WeatherBean data);
}

觀察者和其實現類

public interface Observer {
public void update(WeatherBean data);
}
public class OneObserverDisplay implements Observer, DisplayElement {
public WeatherStation mStation = null;
public OneObserverDisplay(WeatherStation station){
this.mStation = station;
mStation.registerObserver(this);
}
public void display(WeatherBean data) {
System.out.println("this is "   this.getClass().getName()
" getHumidity:"   data.getHumidity()   " getPressure:"
data.getPressure()   " getTemperature:"
data.getTemperature());
}
public void removeListener(){
this.mStation.removeObserver(this);
}
public void update(WeatherBean data) {
display(data);
}
}
public class TwoObserverDisplay implements Observer, DisplayElement {
public WeatherStation mStation = null;
public TwoObserverDisplay(WeatherStation station){
this.mStation = station;
mStation.registerObserver(this);
}
public void display(WeatherBean data) {
System.out.println("this is "   this.getClass().getName()
" getHumidity:"   data.getHumidity()   " getPressure:"
data.getPressure()   " getTemperature:"
data.getTemperature());
}
public void removeListener(){
this.mStation.removeObserver(this);
}
public void update(WeatherBean data) {
display(data);
}
}

ThreeObserverDisplay就不貼了,基本一樣
被觀察者和其實現類

public interface Subject {
void registerObserver(Observer ob);
void removeObserver(Observer ob);
void notifyObservers();
}
public class WeatherStation implements Subject{
private WeatherBean mWeatherBean;
//API 1.5
private ArrayList<Observer> mObservers = new ArrayList<Observer>();
public void setMeasurement(WeatherBean data){
mWeatherBean = data;
measurementChanged();
}
private void measurementChanged(){
notifyObservers();
}
public void registerObserver(Observer ob) {
if(!mObservers.contains(ob)){
mObservers.add(ob);
}
}
public void removeObserver(Observer ob) {
if(mObservers.contains(ob)){
mObservers.remove(ob);
}
}
public void notifyObservers() {
for(Observer observer : mObservers){
observer.update(mWeatherBean);
}
}
}

測試類

public class TestObserver {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
OneObserverDisplay one = new OneObserverDisplay(station);
TwoObserverDisplay two = new TwoObserverDisplay(station);
ThreeObserverDisplay three = new ThreeObserverDisplay(station);
WeatherBean data = new WeatherBean();
data.setHumidity(1.1);
data.setPressure(1.2);
data.setTemperature(1.3);
station.setMeasurement(data);
one.removeListener();
station.setMeasurement(data);
}
}

測試結果:

this is observer.OneObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3
this is observer.TwoObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3
this is observer.ThreeObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3
this is observer.TwoObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3
this is observer.ThreeObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3

使用Java API再寫觀察者的例子

實際上,Java API是支援觀察者的,即上面例子中的Subject和Observer Java API中已經有所定義,即util包中的Observerable和Observer。那麼我們可以不用寫Subject和Observer了
上述例子使用Java API寫出的效果是什麼樣子呢?我的實現是這樣的:
先UML圖就不畫了,類截圖如下:
這裡寫圖片描述
寫法比之前簡化了不少
對比之前的類的截圖,可以發現Subject和Observer不要寫了,因為API已經寫好了。
其次WeatherStation中要刪掉無用程式碼,很多程式碼在Observable中已經幫我們寫好了,廢程式碼我在下面貼出的程式碼中標出了
最後各個Display的removeListener和update方法需要修改,因為Observer已經不是我們寫的Observer,而是Java API中定義好的介面,我們要按介面來實現方法
剩下的都一樣了,怎麼樣轉變是不是很靈活。
我貼一下改動的部分,沒有改動的就不貼了。

首先是被觀察者實現類

public class WeatherStation extends Observable{
private WeatherBean mWeatherBean = null;
//private ArrayList<Observer> mObservers = new ArrayList<Observer>();//不需要了,Observable維護了一個Vector<Observer>陣列
public void setMeasurement(WeatherBean data){
mWeatherBean = data;
measurementChanged();
}
private void measurementChanged(){
setChanged();
notifyObservers(mWeatherBean);
}
/*  
不再需要自己寫deleteObserver和registerObserver方法
Observable中addObserver(Observer ob) 和removeObserver(Observer ob)是現成的 
@Override
public void deleteObserver(Observer ob) {
if(mObservers.contains(ob)){
mObservers.remove(ob);
}
}
public void registerObserver(Observer ob) {
if(!mObservers.contains(ob)){
mObservers.add(ob);
}
}*/
/*
notifyObservers也不需要了,Observable的notifyObservers也寫好了
@Override
public void notifyObservers() {
super.notifyObservers();
for(Observer observer : mObservers){
observer.update(mWeatherBean);
}
}*/
}

其次是各個觀察者,我貼兩個

public class OneObserverDisplay implements Observer, DisplayElement {// 此處的Observer變成了java.util.Observer;
public WeatherStation mStation = null;
public OneObserverDisplay(WeatherStation station) {
this.mStation = station;
mStation.addObserver(this);
}
public void display(WeatherBean data) {
System.out.println("this is "   this.getClass().getName()
" getHumidity:"   data.getHumidity()   " getPressure:"
data.getPressure()   " getTemperature:"
data.getTemperature());
}
public void removeListener() {
this.mStation.deleteObserver(this);
}
public void update(Observable o, Object arg) {
if (arg instanceof WeatherBean) {
display((WeatherBean) arg);
}
}
}
public class TwoObserverDisplay implements Observer, DisplayElement {
public WeatherStation mStation = null;
public TwoObserverDisplay(WeatherStation station) {
this.mStation = station;
mStation.addObserver(this);
}
public void display(WeatherBean data) {
System.out.println("this is "   this.getClass().getName()
" getHumidity:"   data.getHumidity()   " getPressure:"
data.getPressure()   " getTemperature:"
data.getTemperature());
}
public void removeListener() {
this.mStation.deleteObserver(this);
}
public void update(Observable o, Object arg) {
if (arg instanceof WeatherBean) {
display((WeatherBean) arg);
}
}
}

剩下的程式碼和原來一樣了。
輸出結果如下:

this is observer.ThreeObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3
this is observer.TwoObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3
this is observer.OneObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3
this is observer.ThreeObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3
this is observer.TwoObserverDisplay getHumidity:1.1 getPressure:1.2 getTemperature:1.3

兩種情況對比

1.程式碼簡潔
從程式碼簡潔性上看,實現Java API的方式完勝,省略了很多程式碼。
2.輸出順序
對比輸出可以看出,輸出順序略微不同,可以看下被觀察者的notifyObservers的實現:

        for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);

可以看出,這個是倒序通知的。
3.setChange
API中的通知,有個private boolean changed = false;常量,在通知前,會判斷其值,如果其是false是不會通知的。因此,每次通知或者資料變化都需要呼叫setChanged來確保發出通知。
4.變化適應性
其實Java API的設計有點問題,在Java程式設計經驗中有一條:要面向介面程式設計而不是面向實現。
我們發現原先我們自己寫的Subject是個介面,而JAVA API中的Observable是個實體類,這樣如果我們的被觀察者繼承了Observable就無法繼承其他類了,這就限制了被觀察者實現的實體類的擴充套件和複用。
另外Java程式設計提倡多用組合少用繼承。但是Observable將changed的控制方法都設定為protected,這就意味著,即使我們把Observable的實體類組合到Observer實體類中,也無法控制changed因為protected只在本類及其子類可用。這違反了用組合少用繼承,使得程式不夠靈活,有時會限制程式的擴充套件。

總結,再看例項

因此,JAVA API的觀察者模式的實現雖然減少了不少程式碼,但是也存在諸多擴充套件和靈活性問題。如果JAVA API提供的方式已經夠用,那麼就可以使用簡便的JAVA API方式,但是如果覺得程式後期需要擴充套件的可能性大,那麼最好還是不要閒麻煩,自己實現一下。對比之前的總結,我覺得還是當前這個版本比較通俗易懂。

在Java和Android系統中,觀察者模式的例子很常見,比如Swing元件和Android Button控制元件的點選事件監聽。Android 的廣播和廣播接收者,也是利用註冊和解除註冊來實現接收廣播的,至少形式上很相似。
這就像從報社訂報紙,每個使用者可以選擇定或者不定報紙。報社每月會遍歷訂閱使用者列表,給他們寄去報紙;如果使用者中途取消訂閱,報社就刪除該使用者,下次不再寄去報紙。其他沒有訂閱的使用者如果感興趣了,也可以訂閱,報社會在列表加入該使用者,下次寄去報紙給該使用者。