1. 程式人生 > >Head First設計模式之觀察者模式(Observer Pattern)

Head First設計模式之觀察者模式(Observer Pattern)

前言:

     這一節開始學習觀察者模式,開始講之前會先像第一節那樣通過一個應用場景來引入該模式。具體場景為:氣象站提供了一個WeatherData物件,該物件可以追蹤獲取天氣的溫度、氣壓、溼度資訊,WeatherData物件會隨即更新三個佈告板的顯示:目前狀況(溫度、溼度、氣壓)、氣象統計和天氣預報。

1.   基本需求:利用WeatherData物件獲取資料、並更新三個佈告板:目前狀況、氣象統計和天氣預報

WeatherData類圖如下:


說明:

    GetTemperature()、GetHumidity()、GetPressure()分別用來獲取天氣溫度、溼度氣壓,MeasurementsChanged()當氣象測量更新此方法會被呼叫。

分析:

1.      可以通過WeatherData的Getter方法獲取三個測量值:溫度、溼度、氣壓

2.      氣象測量更新時會呼叫MeasurementsChanged()方法

3.      需要實現三個天氣資料佈告板:“目前狀況”、“氣象統計”、“天氣預報”,一旦WeatherData測到新值,這些佈告也馬上更新

4.      此係統可擴充套件,可以隨意的新增或者刪除任何佈告板。

實現:

public class WeatherData
{
   public void MeasurementsChanged()
    {
        float temp = GetTemperature();
        float humidity = GetHumidity();
        float pressure = GetPressure();
 
        currentConditionDisplay.update(temp,humidity, pressure);
        statisticsDisplay.update(temp,humidity, pressure);
        forecastDisplay.update(temp, humidity,pressure);
    }
    //其他方法
}

反思:

我們的這種實現有何不妥?

結合我們第一節中的一些面向物件的原則,這種實現方式會有如下問題:

l  針對具體實現程式設計,後續新增或者刪除佈告板必須修改程式

l  未將改變的地方封裝起來

這種實現方式與面向物件的一些基本原則是相違背的,目前暫時是實現了使用者的需求,但是在後續不具備可擴充套件行。

2.   引入觀察者模式

2.1 出版者+訂閱者=觀察者模式

出版者:就相當於“主題”(Subject),訂閱者相當於“觀察者”(Observer)

當出版者(主題)發行新的報紙的時候,所有的觀察者(訂閱者)就可以收到最新的報紙,同時,當新的觀察者(訂閱者)加入時,也可以收到最新的報紙,當觀察者(訂閱者)退訂報紙後,就再也收不到新的報紙。

2.2 定義觀察者模式

觀察者模式定義了物件之間一對多依賴,這樣一來,當一個物件狀態改變時,它的所有依賴者都會收到通知並自動更新。

主題和觀察者定義一對多的關係。觀察者依賴於此主題,只要主題狀態一有變化,觀察者就會被通知,根據通知的風格,觀察者可能因此新值而更新。

觀察者模式:類圖

2.3設計原則:為了互動物件之間的鬆耦合設計而努力

鬆耦合的威力

當兩個物件之間鬆耦合,它們依然可以互動,但是不清楚彼此的細節。

觀察者模式提供了一種物件設計,讓主題和觀察者之間鬆耦合。

說明:

1.      主題只知道觀察者實現了某個介面(IObserver介面),不需要知道觀察者是誰,或其他細節。

2.      任何時候都可以增加或者刪除的觀察者,主題唯一依賴的是一個實現了IObserver介面的物件列表。

3.      新的型別觀察者出現時,主題程式碼不需要修改,只需要在新型別裡實現觀察者介面,然後註冊為觀察者即可。

4.      可以獨立的複用主題或觀察者,因為二者鬆耦合。

5.      改變主題或者觀察者,並不會影響另一方。因為二者鬆耦合。

鬆耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因為物件之間的相互依賴講到了最低。

3.   利用觀察者模式設計並實現氣象站

3.1 設計氣象站

類圖:

3.2具體實現

3.2.1主題、觀察者、顯示介面
/// Description:物件、觀察者、顯示介面
    /// </summary>
    public interface ISubject
    {
        void RegisterObserver(IObserver o);//註冊觀察者
        void RemoveObserver(IObserver o);//刪除觀察者
        void NotifyObervers();//通知觀察者
    }
 
    public interface IObserver
    {
        void Update(float temp, float humidity,float pressure);
    }
 
    public interface IDisplayElement
    {
        void Display();
}
3.2.2 WeatherData類:註冊、刪除、通知觀察者

/// Description:WeatherData 註冊、刪除、通知觀察者
    /// </summary>
    public class WeatherData:ISubject
    {
        private ArrayList observers;
        private float temperature;
        private float humidity;
        private float pressure;
 
        public WeatherData()
        {
            observers = new ArrayList();//初始化obervers,用來儲存註冊的觀察者
        }
 
        /// <summary>
        /// 註冊觀察者
        /// </summary>
        /// <paramname="o"></param>
        public void RegisterObserver(IObservero)
        {
            observers.Add(o);
        }
 
        /// <summary>
        /// 刪除觀察者
        /// </summary>
        /// <paramname="o"></param>
        public void RemoveObserver(IObserver o)
        {
            int i = observers.IndexOf(o);
            if (i >= 0)
                observers.Remove(o);
        }
 
        /// <summary>
        /// 通知觀察者
        /// </summary>
        public void NotifyObervers()
        {
            foreach (IObserver o in observers)
            {
                o.Update(temperature, humidity,pressure);
            }
        }
 
        /// <summary>
        /// 當從氣象站得到更新觀測值時,通知觀察者
        /// </summary>
        public void MeasurementsChanged()
        {
            NotifyObervers();
        }
 
        /// <summary>
        ///
        /// </summary>
        /// <paramname="temperature"></param>
        /// <paramname="humidity"></param>
        /// <paramname="pressure"></param>
        public void SetMeasurements(floattemperature, float humidity, float pressure)
        {
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
            MeasurementsChanged();
        }
    }
3.2.3 佈告板類,實現了IObserver、IDisplayElement介面
 /// Description:建立佈告板
    /// </summary>
    public classCurrentConditionsDisplay:IObserver,IDisplayElement
    {
        private float temperature;
        private float humidity;
        private ISubject weatherData;
 
        public CurrentConditionsDisplay(ISubject weatherData)
        {
            this.weatherData = weatherData;
            weatherData.RegisterObserver(this);
        }
 
        public void Update(float temperature,float humidity, float pressure)
        {
            this.temperature = temperature;
            this.humidity = humidity;
            Display();
        }
        public void Display()
        {
            Console.WriteLine("Currentcoditions: " + temperature + "F degress and " + humidity +"% humidity");
        }
    }
3.2.4 測試
WeatherStation.WeatherDataweatherData = new WeatherStation.WeatherData();
WeatherStation.CurrentConditionsDisplaycurrentDisplay = new WeatherStation.CurrentConditionsDisplay(weatherData);
weatherData.SetMeasurements(10,20, 30);

結果如下:


4.   Java內建的觀察者模式

Java內建的觀察者模式,許多功能都已經事先準備好了,甚至可以用推(push)或拉(pull)的方式傳送資料。

使用java內建觀察者模式實現氣象站的OO設計類圖,如下:


Java內建觀察者模式與我們在3小節中明顯的差異是WeatherData繼承自Observable類,並集成了一些增加、刪除、通知觀察者的方法。

l  將物件變成觀察者

首先還是要實現Observer(觀察者)介面,其次呼叫Observable物件的addObserver()方法即可。

l  可觀察者(主題)送出通知

1.      先呼叫setChanged()方法,標記狀態已經改變的事實。

2.      呼叫notifyObservers()方法(該方法有2個,任意一個皆可):notifyObservers()或notifyObservers(Object arg)

l  觀察者接收通知

觀察者實現了Observer的介面,方法簽名如下:

update(Observable o,Object arg)

第一個引數為主題本身,讓觀察者知道是哪個主題通知它的

第二個引數是傳入notifyObservers()的資料物件

如果想用“推”的方式將資料給觀察者,則可以把資料當做資料物件的方式傳給notifyObservers(arg)

如果想用“拉”的方式將資料給觀察者,則需要在update()中,通過WeatherData的getTemperature()等方法獲取對應的氣象值。

缺陷:

Java內建的觀察者模式中Observable是一個類,你必須設計一個類去繼承它。如果某類相同時具有Observable類和另一個超類的行為,就無法實現,因為java不支援多繼承。

同時也限制了Observable的複用能力。

同時,Observable API將setChanged()方法保護了起來,除非繼承自Observable類,否則無法建立Observable例項組合到自己的物件中,也違背了面向物件設計的第二個原則:多用組合,少用繼承。

5.   總結

OO原則:

封裝變化

多用組合,少用繼承

針對介面程式設計,不針對實現程式設計

對互動物件之間的鬆耦合設計而努力(新的OO原則,鬆耦合的設計更有彈性,更能應對變化)

OO模式:

觀察者模式—在物件之間定義一對多的依賴,這樣一來,當一個物件改變狀態,依賴它的物件都會收到通知,並自動更新。