解析.NET觀察者模式(Observer Pattern)
概要
觀察者模式是一種設計模式。設計模式是面向對象思想的集大成,GOF在其經典著作中總結了23種設計模式,又可分為:創建型、結構型和行為型3個大類,其中觀察者模式屬于行為型模式。
目錄
觀察者模式定義
實現觀察者模式的過程
觀察者模式結構
觀察者模式實例
觀察者模式總結
一、觀察者模式定義
1.觀察者模式定義了對象間的一對多依賴關系。當一方的對象改變狀態時,所有的依賴者都會被通知并自動被更新。
2.在觀察者模式中,被依賴的一方叫做目標或主題(Subject),依賴方叫做觀察者(Observers)。
二、實現觀察者模式的過程
實現觀察者模式有很多形式,比較直觀的是使用一種“注冊--通知--撤銷注冊”的形式。下面的三個圖詳細的描述了這樣一種過程:
1.觀察者
(Observer)將自己注冊到被觀察對象(Subject)中,被觀察對象將觀察者存放在一個容器(Container)里。

圖一
2.被觀察對象
被觀察對象發生了某種變化(如圖中的SomeChange),從容器中得到所有注冊過的觀察者,將變化通知觀察者。

圖二
3.撤銷觀察
觀察者告訴被觀察者要撤銷觀察,被觀察者從容器中將觀察者去除。

圖三
觀察者將自己注冊到被觀察者的容器中時,被觀察者不應該過問觀察者的具體類型,而是應該使用觀察者的接口。這樣的優點是:假定程序中還有別的觀察者,那么只要這個觀察者也是相同的接口實現即可。一個被觀察者可以對應多個觀察者,當被觀察者發生變化的時候,他可以將消息一一通知給所有的觀察者。基于接口,而不是具體的實現——這一點為程序提供了更大的靈活性。
#p#
三、觀察者模式結構
觀察者模式是對象的行為型模式,又叫做發表-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-收聽者(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式結構類圖如下:

圖四
1.Subject(主題接口)
1.1規定ConcreteSubject的統一接口;
1.2每個Subject可以有多個Observer;
2.ConcreteSubject(具體主題)
2.1維護對所有具體觀察者的引用的列表;
2.2狀態發生變化時會發送通知給所有注冊的觀察者。
3.Observer(觀察者接口)
3.1規定ConcreteObserver的統一接口;
3.2定義了一個update()方法,在被觀察對象狀態改變時會被調用。
4.ConcreteObserver(具體觀察者)
4.1維護一個對ConcreteSubject的引用;
4.2特定狀態與ConcreteSubject同步;
4.3實現Observer接口,通過update()方法接收ConcreteSubject的通知。
四、觀察者模式實例
在之前,Bohan向我們分享了一個關于開發下一代基于因特網的天氣監測系統的實例。該監測系統涉及到的參數一共有三種:temperature, humidity, pressure,外界接受到的信息也以三種布告板顯示:currentConditionDisplay會列出當前的氣溫,濕度和氣壓;statisticsDisplay會列出當前的溫度最高,最低和平均值;forecastDisplay列出將來預測的天氣狀況。
1.普通實現方法
現在我們用最容易想到的一個方法來實現系統:
- 天氣檢測系統普通做法
- 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);
- }
- }
的確,該方法可以實現這個系統,但是仔細想想面向對象的設計原則就知道這種實現是有問題的:
- currentConditionDisplay.update(temp,humidity,pressure);
- statisticsDisplay.update(temp,humidity,pressure);
- forecastDisplay.update(temp,humidity,pressure);
這里違背了“面向接口編程,而不要面向實現編程”的原則,會使我們在增加或刪除不同的布告板時必須修改程序。也就是說,布告板相關的部分是系統中最不穩定的部分,應該將其單獨隔離開。針對這個問題,我們可以想到曾經學過的另一個原則:“找到系統中變化的部分,將變化的部分同其它穩定的部分隔開”。因此,我們將使用觀察者模式來實現該天氣監測系統。
2.觀察者模式實現方法
就天氣監測系統問題的應用場景來說,WeatherData可以作為ConcreteSubject(具體主題)來看待,而不同的布告板(currentConditionDisplay、statisticsDisplay、forecastDisplay)則可以作為ConcreteObserver(具體觀察者)來看待,也就是說布告板觀察WeatherData對象,如果WeatherData對象有任何狀態變化,則立刻更新布告板的數據信息。 下面是詳細的代碼實現:
2.1主題接口ISubject.cs:
- public interface ISubject
- {
- void RegisterObserver(IObserver o);
- void RemoveObserver(IObserver o);
- void NotifyObserver();
- }
2.2觀察者接口IObserver.cs:
- public interface IObserver
- { //給update()方法定義了三個對應不同氣象數據的參數。
- void Update(float temperature, float humidity, float pressure);
- }
2.3用于顯示結果的接口IDisplayElement.cs:
- public interface IDisplayElement
- {
- void Display();
- }
2.4具體主題WeatherData.cs:
這個類是ISubject的具體實現,內部使用ArrayList來記錄所有注冊的觀察者,SetMeasurements() 方法是用來模擬在天氣狀況改變的時候自動觸發MeasurementsChanged()方法的機制。
- 具體主題WeatherData
- public class WeatherData : ISubject
- {
- private ArrayList observers;
- private float temperature;
- private float humidity;
- private float pressure;
- public WeatherData()
- {
- observers = new ArrayList();
- }
- #region ISubject Members
- //注冊觀察者
- public void RegisterObserver(IObserver o)
- {
- observers.Add(o);
- }
- //移除觀察者
- public void RemoveObserver(IObserver o)
- {
- int i = observers.IndexOf(o);
- if(i >= 0)
- {
- observers.Remove(o);
- }
- }
- //通知所有觀察者
- public void NotifyObserver()
- {
- foreach(IObserver observer in observers)
- {
- observer.Update(temperature,humidity,pressure);
- }
- }
- #endregion
- public void MeasurementsChanged()
- { //更新數據
- NotifyObserver();
- }
- public void SetMeasurements(float temperature, float humidity,float pressure)
- {
- this.temperature = temperature;
- this.humidity = humidity;
- this.pressure = pressure;
- MeasurementsChanged();
- }
- }
#p#
2.5具體觀察者:
- CurrentConditionsDisplay.cs:
這個類是IObserver和IDisplayElement的具體實現,代表顯示當前天氣狀況的具體布告板對象,其內部維護了一個ISubject類型的變量,該變量在CurrentConditionsDisplay的構造函數中被初始化,同時調用ISubject.registerObserver()方法,實現訂閱ISubject。
- CurrentConditionsDisplay
- public class CurrentConditionsDisplay :IObserver, IDisplayElement
- {
- private float temperature;
- private float humidity;
- private float pressure;
- private ISubject weatherData;
- public CurrentConditionsDisplay(ISubject weatherData)
- {
- this.weatherData = weatherData;
- weatherData.RegisterObserver(this);
- }
- #region IObserver Members
- public void Update(float temperature, float humidity, float pressure)
- {
- //獲取更新的溫度數據
- //獲取更新的濕度數據
- //獲得更新的氣壓數據
- this.temperature = temperature;
- this.humidity = humidity;
- this.pressure = pressure;
- Display();
- }
- #endregion
- #region IDisplayElement Members
- public void Display() //顯示當前觀測值
- {
- Console.WriteLine( "Current conditions: " + temperature +"F degrees and " + humidity + "% humidity and "+pressure+"f pressure");
- }
- #endregion
- }
- ForcastDisplay.cs:
- ForcastDisplay
- public class ForcastDisplay : IObserver, IDisplayElement
- { //顯示預測的天氣預報的觀察者
- private float currentPressure = 30.2f;
- private float lastPressure;
- private ISubject weatherData;
- public ForcastDisplay(ISubject weatherData)
- {
- this.weatherData = weatherData;
- weatherData.RegisterObserver(this);
- }
- #region IObserver Members
- // 獲取更新的氣壓數據
- public void Update(float temperature, float humidity, float pressure)
- {
- lastPressure = currentPressure;
- currentPressure = pressure;
- Display();
- }
- #endregion
- #region IDisplayElement Members
- //顯示預測的天氣預報
- public void Display()
- {
- StringBuilder sb = new StringBuilder();
- sb.Append("Forecast: ");
- if (currentPressure > lastPressure)
- {
- sb.Append("Improving weather on the way!");
- }
- else if (currentPressure == lastPressure)
- {
- sb.Append("More of the same");
- }
- else if (currentPressure < lastPressure)
- {
- sb.Append("Watch out for cooler, rainy weather");
- }
- Console.WriteLine(sb.ToString());
- }
- #endregion
- }
- StatisticsDisplay.cs:
- StatisticsDisplay
- public class StatisticsDisplay : IObserver, IDisplayElement
- {
- //顯示平均、最大、最小觀測值的觀察者
- //F為華氏溫度,攝氏=5/9(F-32)
- #region Members
- private float maxTemp = 0.0f;
- private float minTemp = 200;
- private float temperatureSum = 0.0f;
- private int numReadings = 0;
- private ISubject weatherData;
- #endregion//Members
- #region NumberOfReadings Property
- public int NumberOfReadings
- {
- get
- {
- return numReadings;
- }
- }
- #endregion//NumberOfReadings Property
- #region Constructor
- public StatisticsDisplay(ISubject weatherData)
- {
- this.weatherData = weatherData;
- weatherData.RegisterObserver(this);
- }
- #endregion///Constructor
- #region IObserver Members
- //獲取更新的溫度數據
- public void Update(float temperature, float humidity, float pressure)
- {
- temperatureSum += temperature;
- numReadings++;
- if (temperature > maxTemp)
- {
- maxTemp = temperature;
- }
- if (temperature < minTemp)
- {
- minTemp = temperature;
- }
- Display();
- }
- #endregion
- #region IDisplayElement Members
- public void Display()
- {
- Console.WriteLine("Avg/Max/Min temperature = " +(temperatureSum / numReadings)+ "F/" + maxTemp + "F/" + minTemp + "F");
- }
- #endregion
- }
#p#
2.6主類實現Program.cs:
- 主類實現
- public class program
- {//實現天氣監測系統的主類
- public static void Main(String[] args)
- {
- WeatherData weatherData = new WeatherData();
- ForcastDisplay forecastDisplay = new ForcastDisplay(weatherData);
- StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
- CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
- weatherData.SetMeasurements(84, 66, 31.6f);
- weatherData.SetMeasurements(88, 72, 30.0f);
- weatherData.SetMeasurements(86, 85, 30.0f);
- }
- }
2.7運行后結果如下:
- Forecast: Improving weather on the way!
- Avg/Max/Min temperature = 84F/84F/84F
- Current conditions: 84F degrees and 66% humidity and 31.6f pressure
- Forecast: Watch out for cooler, rainy weather
- Avg/Max/Min temperature = 86F/88F/84F
- Current conditions: 88F degrees and 72% humidity and 30f pressure
- Forecast: More of the same
- Avg/Max/Min temperature = 86F/88F/84F
- Current conditions: 86F degrees and 85% humidity and 30f pressure
五、觀察者模式總結
1.觀察者模式有以下的優點:
1.1觀察者模式在被觀察者和觀察者之間建立一個抽象的耦合。被觀察者角色所知道的只是一個具體觀察者列表,每一個具體觀察者都符合一個抽象觀察者的接口。被觀察者并不認識任何一個具體觀察者,它只知道它們都有一個共同的接口。
由于被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。如果被觀察者和觀察者都被扔到一起,那么這個對象必然跨越抽象化和具體化層次。
1.2觀察者模式支持廣播通訊,被觀察者會向所有的登記過的觀察者發出通知。
1.3通過Observer模式,把一對多對象之間的通知依賴關系的變得更為松散,大大地提高了程序的可維護性和可擴展性,也很好的符合了開放-封閉原則。
2.觀察者模式有下面的缺點:
2.1如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
2.2如果在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,導致系統崩潰。在使用觀察者模式時要特別注意這一點。
2.3如果對觀察者的通知是通過另外的線程進行異步投遞的話,系統必須保證投遞是以自恰的方式進行的。
2.4雖然觀察者模式可以隨時使觀察者知道所觀察的對象發生了變化,但是觀察者模式沒有相應的機制使觀察者知道所觀察的對象是怎么發生變化的。
3.觀察者模式的適用性:
3.1當一個抽象模型有兩個方面, 其中一個方面依賴于另一方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。
3.2當一個對象的改變需要同時改變其它對象, 而不知道具體有多少對象有待改變。
3.3當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之, 你不希望這些對象是緊密耦合的。
源碼下載:天氣監測系統的源碼 Weather
原文鏈接:http://www.cnblogs.com/YZDONET/archive/2012/08/15/2639702.html
























