解析.NET觀察者模式(Observer Pattern)
概要
觀察者模式是一種設(shè)計(jì)模式。設(shè)計(jì)模式是面向?qū)ο笏枷氲募蟪?,GOF在其經(jīng)典著作中總結(jié)了23種設(shè)計(jì)模式,又可分為:創(chuàng)建型、結(jié)構(gòu)型和行為型3個(gè)大類,其中觀察者模式屬于行為型模式。
目錄
觀察者模式定義
實(shí)現(xiàn)觀察者模式的過程
觀察者模式結(jié)構(gòu)
觀察者模式實(shí)例
觀察者模式總結(jié)
一、觀察者模式定義
1.觀察者模式定義了對象間的一對多依賴關(guān)系。當(dāng)一方的對象改變狀態(tài)時(shí),所有的依賴者都會(huì)被通知并自動(dòng)被更新。
2.在觀察者模式中,被依賴的一方叫做目標(biāo)或主題(Subject),依賴方叫做觀察者(Observers)。
二、實(shí)現(xiàn)觀察者模式的過程
實(shí)現(xiàn)觀察者模式有很多形式,比較直觀的是使用一種“注冊--通知--撤銷注冊”的形式。下面的三個(gè)圖詳細(xì)的描述了這樣一種過程:
1.觀察者
(Observer)將自己注冊到被觀察對象(Subject)中,被觀察對象將觀察者存放在一個(gè)容器(Container)里。
圖一
2.被觀察對象
被觀察對象發(fā)生了某種變化(如圖中的SomeChange),從容器中得到所有注冊過的觀察者,將變化通知觀察者。
圖二
3.撤銷觀察
觀察者告訴被觀察者要撤銷觀察,被觀察者從容器中將觀察者去除。
圖三
觀察者將自己注冊到被觀察者的容器中時(shí),被觀察者不應(yīng)該過問觀察者的具體類型,而是應(yīng)該使用觀察者的接口。這樣的優(yōu)點(diǎn)是:假定程序中還有別的觀察者,那么只要這個(gè)觀察者也是相同的接口實(shí)現(xiàn)即可。一個(gè)被觀察者可以對應(yīng)多個(gè)觀察者,當(dāng)被觀察者發(fā)生變化的時(shí)候,他可以將消息一一通知給所有的觀察者?;诮涌冢皇蔷唧w的實(shí)現(xiàn)——這一點(diǎn)為程序提供了更大的靈活性。
#p#
三、觀察者模式結(jié)構(gòu)
觀察者模式是對象的行為型模式,又叫做發(fā)表-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-收聽者(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式結(jié)構(gòu)類圖如下:
圖四
1.Subject(主題接口)
1.1規(guī)定ConcreteSubject的統(tǒng)一接口;
1.2每個(gè)Subject可以有多個(gè)Observer;
2.ConcreteSubject(具體主題)
2.1維護(hù)對所有具體觀察者的引用的列表;
2.2狀態(tài)發(fā)生變化時(shí)會(huì)發(fā)送通知給所有注冊的觀察者。
3.Observer(觀察者接口)
3.1規(guī)定ConcreteObserver的統(tǒng)一接口;
3.2定義了一個(gè)update()方法,在被觀察對象狀態(tài)改變時(shí)會(huì)被調(diào)用。
4.ConcreteObserver(具體觀察者)
4.1維護(hù)一個(gè)對ConcreteSubject的引用;
4.2特定狀態(tài)與ConcreteSubject同步;
4.3實(shí)現(xiàn)Observer接口,通過update()方法接收ConcreteSubject的通知。
四、觀察者模式實(shí)例
在之前,Bohan向我們分享了一個(gè)關(guān)于開發(fā)下一代基于因特網(wǎng)的天氣監(jiān)測系統(tǒng)的實(shí)例。該監(jiān)測系統(tǒng)涉及到的參數(shù)一共有三種:temperature, humidity, pressure,外界接受到的信息也以三種布告板顯示:currentConditionDisplay會(huì)列出當(dāng)前的氣溫,濕度和氣壓;statisticsDisplay會(huì)列出當(dāng)前的溫度最高,最低和平均值;forecastDisplay列出將來預(yù)測的天氣狀況。
1.普通實(shí)現(xiàn)方法
現(xiàn)在我們用最容易想到的一個(gè)方法來實(shí)現(xiàn)系統(tǒng):
- 天氣檢測系統(tǒng)普通做法
- Public class WeatherData {
- //聲明實(shí)例變量…
- 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);
- }
- }
的確,該方法可以實(shí)現(xiàn)這個(gè)系統(tǒng),但是仔細(xì)想想面向?qū)ο蟮脑O(shè)計(jì)原則就知道這種實(shí)現(xiàn)是有問題的:
- currentConditionDisplay.update(temp,humidity,pressure);
- statisticsDisplay.update(temp,humidity,pressure);
- forecastDisplay.update(temp,humidity,pressure);
這里違背了“面向接口編程,而不要面向?qū)崿F(xiàn)編程”的原則,會(huì)使我們在增加或刪除不同的布告板時(shí)必須修改程序。也就是說,布告板相關(guān)的部分是系統(tǒng)中最不穩(wěn)定的部分,應(yīng)該將其單獨(dú)隔離開。針對這個(gè)問題,我們可以想到曾經(jīng)學(xué)過的另一個(gè)原則:“找到系統(tǒng)中變化的部分,將變化的部分同其它穩(wěn)定的部分隔開”。因此,我們將使用觀察者模式來實(shí)現(xiàn)該天氣監(jiān)測系統(tǒng)。
2.觀察者模式實(shí)現(xiàn)方法
就天氣監(jiān)測系統(tǒng)問題的應(yīng)用場景來說,WeatherData可以作為ConcreteSubject(具體主題)來看待,而不同的布告板(currentConditionDisplay、statisticsDisplay、forecastDisplay)則可以作為ConcreteObserver(具體觀察者)來看待,也就是說布告板觀察WeatherData對象,如果WeatherData對象有任何狀態(tài)變化,則立刻更新布告板的數(shù)據(jù)信息。 下面是詳細(xì)的代碼實(shí)現(xiàn):
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()方法定義了三個(gè)對應(yīng)不同氣象數(shù)據(jù)的參數(shù)。
- void Update(float temperature, float humidity, float pressure);
- }
2.3用于顯示結(jié)果的接口IDisplayElement.cs:
- public interface IDisplayElement
- {
- void Display();
- }
2.4具體主題WeatherData.cs:
這個(gè)類是ISubject的具體實(shí)現(xiàn),內(nèi)部使用ArrayList來記錄所有注冊的觀察者,SetMeasurements() 方法是用來模擬在天氣狀況改變的時(shí)候自動(dòng)觸發(fā)MeasurementsChanged()方法的機(jī)制。
- 具體主題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()
- { //更新數(shù)據(jù)
- 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:
這個(gè)類是IObserver和IDisplayElement的具體實(shí)現(xiàn),代表顯示當(dāng)前天氣狀況的具體布告板對象,其內(nèi)部維護(hù)了一個(gè)ISubject類型的變量,該變量在CurrentConditionsDisplay的構(gòu)造函數(shù)中被初始化,同時(shí)調(diào)用ISubject.registerObserver()方法,實(shí)現(xiàn)訂閱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)
- {
- //獲取更新的溫度數(shù)據(jù)
- //獲取更新的濕度數(shù)據(jù)
- //獲得更新的氣壓數(shù)據(jù)
- this.temperature = temperature;
- this.humidity = humidity;
- this.pressure = pressure;
- Display();
- }
- #endregion
- #region IDisplayElement Members
- public void Display() //顯示當(dāng)前觀測值
- {
- Console.WriteLine( "Current conditions: " + temperature +"F degrees and " + humidity + "% humidity and "+pressure+"f pressure");
- }
- #endregion
- }
- ForcastDisplay.cs:
- ForcastDisplay
- public class ForcastDisplay : IObserver, IDisplayElement
- { //顯示預(yù)測的天氣預(yù)報(bào)的觀察者
- private float currentPressure = 30.2f;
- private float lastPressure;
- private ISubject weatherData;
- public ForcastDisplay(ISubject weatherData)
- {
- this.weatherData = weatherData;
- weatherData.RegisterObserver(this);
- }
- #region IObserver Members
- // 獲取更新的氣壓數(shù)據(jù)
- public void Update(float temperature, float humidity, float pressure)
- {
- lastPressure = currentPressure;
- currentPressure = pressure;
- Display();
- }
- #endregion
- #region IDisplayElement Members
- //顯示預(yù)測的天氣預(yù)報(bào)
- 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
- //獲取更新的溫度數(shù)據(jù)
- 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主類實(shí)現(xiàn)Program.cs:
- 主類實(shí)現(xiàn)
- public class program
- {//實(shí)現(xiàn)天氣監(jiān)測系統(tǒng)的主類
- 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運(yùn)行后結(jié)果如下:
- 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
五、觀察者模式總結(jié)
1.觀察者模式有以下的優(yōu)點(diǎn):
1.1觀察者模式在被觀察者和觀察者之間建立一個(gè)抽象的耦合。被觀察者角色所知道的只是一個(gè)具體觀察者列表,每一個(gè)具體觀察者都符合一個(gè)抽象觀察者的接口。被觀察者并不認(rèn)識(shí)任何一個(gè)具體觀察者,它只知道它們都有一個(gè)共同的接口。
由于被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。如果被觀察者和觀察者都被扔到一起,那么這個(gè)對象必然跨越抽象化和具體化層次。
1.2觀察者模式支持廣播通訊,被觀察者會(huì)向所有的登記過的觀察者發(fā)出通知。
1.3通過Observer模式,把一對多對象之間的通知依賴關(guān)系的變得更為松散,大大地提高了程序的可維護(hù)性和可擴(kuò)展性,也很好的符合了開放-封閉原則。
2.觀察者模式有下面的缺點(diǎn):
2.1如果一個(gè)被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間。
2.2如果在被觀察者之間有循環(huán)依賴的話,被觀察者會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,導(dǎo)致系統(tǒng)崩潰。在使用觀察者模式時(shí)要特別注意這一點(diǎn)。
2.3如果對觀察者的通知是通過另外的線程進(jìn)行異步投遞的話,系統(tǒng)必須保證投遞是以自恰的方式進(jìn)行的。
2.4雖然觀察者模式可以隨時(shí)使觀察者知道所觀察的對象發(fā)生了變化,但是觀察者模式?jīng)]有相應(yīng)的機(jī)制使觀察者知道所觀察的對象是怎么發(fā)生變化的。
3.觀察者模式的適用性:
3.1當(dāng)一個(gè)抽象模型有兩個(gè)方面, 其中一個(gè)方面依賴于另一方面。將這二者封裝在獨(dú)立的對象中以使它們可以各自獨(dú)立地改變和復(fù)用。
3.2當(dāng)一個(gè)對象的改變需要同時(shí)改變其它對象, 而不知道具體有多少對象有待改變。
3.3當(dāng)一個(gè)對象必須通知其它對象,而它又不能假定其它對象是誰。換言之, 你不希望這些對象是緊密耦合的?! ?/p>
源碼下載:天氣監(jiān)測系統(tǒng)的源碼 Weather
原文鏈接:http://www.cnblogs.com/YZDONET/archive/2012/08/15/2639702.html