前言: 這篇文章我們以Head First設計模式中講解的氣象站為例,通過它的案列進行學分析和編碼(C#)測試,並歸納總結出觀察者模式。
1、氣象監測案列,錯誤示范實現
一個氣象站,分別有三個裝置:溫度感應裝置,濕度感應裝置,氣壓感應裝置。WeathData對象跟蹤氣象站數據,WeathData有MeasurmentsChanged()方法,當感應裝置數據變化后就會調用MeasurmentsChanged對使用改數據的用戶進行數據更新。目前需求是要三個布告板,分別是目前氣象數據狀況布告板(CurrentConditionDisply)、氣象數據統計布告板(StaisticsDisply)、天氣預報布告板(ForcastDisply)。三塊布告板都是需要接收氣象站數據,然后按需展示到布告板上。針對這個需求我們可以如下方式實現:
public class WeatherData(){ private float Temperature{get;set;} private float Humidity{get;set;} private float Pressure{get;set;} public void MeasurmentsChanged(){ CurrentConditionDisply.Update(Temperature,Humidity,Pressure); StaisticsDisply.Update(Temperature,Humidity,Pressure); ForcastDisply.Update(Temperature,Humidity,Pressure); } } public class CurrentConditionDisply{ public void Update(float temperature,float humidity,float Pressure){ //更新公布數據 } } public class StaisticsDisply{ public void Update(float temperature,float humidity,float Pressure){ //更新統計數據 } } public class ForcastDisply{ public void Update(float temperature,float humidity,float Pressure){ //更新天氣預報 } }
WeatherData是數據跟蹤對象,當氣象站數據變化時用MeasurmentsChanged方法來依次調用三塊布告板的Update方法更新氣象數據。按照這種設計能實現目前需求,但是如果新加入一種布告板或者刪除一個布告板,那么我們需要去修改MeasurmentsChanged方法新增或者刪除代碼,這就會造成后期的維護擴展問題。這個例子暴露的問題:
1、我們是針對實現編程,而非針對接口。
2、對於每個新的布告板,我們都得修改代碼。
3、無法在運行時動態地增加或者刪除布告板。
4、未封裝改變的部分,違反了對修改關閉,對擴展開放原則。
2、使用觀察者模式解耦
由1的實現和帶來的問題以及它的場景我們可以使用設計模式中的觀察者模式很好的滿足這一需求,且后面的維護擴展都很方便。首先我們先了解觀察者模式
觀察者模式:定義了對象之間的一對多依賴,當一個對象改變時,他的所有依賴都會收到通知並自動更新。
訂閱報紙就是典型的觀察者模式,出版社即為主題(subject),訂閱者即是觀察者(observer),當有新報紙時,報社就會派人送新報紙到訂閱了該報紙的讀者手上。我們通過觀察者模式類圖進行理解我記憶,然后我們再對之前的氣象站進行觀察者模式封裝修改。
3、利用觀察者模式改進氣象站
按照觀察者模式我們需要定義一個主題接口Subject,WeatherData作為具體的主題類繼承接口Subject,實現注冊移除通知觀察者接口。定義Observer接口,其他三塊布告板繼承Observer實現自己的更新數據方法Update。
/// <summary> /// 主題 /// </summary> public interface Subject { public void RegisterObserver(Observer o); public void RemoveObserver(Observer o); public void NotifyObserver(); } /// <summary> /// 具體主題(氣象站) /// </summary> public class WeatherData : Subject { private List<Observer> observers; private float Temperature { get; set; } private float Humidity { get; set; } private float Pressure { get; set; } public WeatherData() { observers = new List<Observer>(); } public void RegisterObserver(Observer o) { observers.Add(o); } public void RemoveObserver(Observer o) { observers.Remove(o); } //通知觀察者 public void NotifyObserver() { foreach (var o in observers) { o.Update(Temperature, Humidity, Pressure); } } public void MeasurmentsChanged() { NotifyObserver(); } //數據變化 public void SetMeasurments(float temperature, float humidity, float pressure) { Temperature = temperature; Humidity = humidity; Pressure = pressure; MeasurmentsChanged(); } } /// <summary> /// 訂閱者 /// </summary> public interface Observer { void Update(float temperature, float humidity, float pressure); } public class CurrentConditionDisply : Observer { private Subject weatherData; public CurrentConditionDisply(Subject weatherData) { this.weatherData = weatherData; weatherData.RegisterObserver(this); } public void Update(float temperature, float humidity, float pressure) { Console.WriteLine($"當前情況布告板:{temperature},{humidity},{pressure}"); } } public class StaisticsDisply : Observer { private Subject weatherData; public StaisticsDisply(Subject weatherData) { this.weatherData = weatherData; weatherData.RegisterObserver(this); } public void Update(float temperature, float humidity, float pressure) { Console.WriteLine($"統計數據布告板:{temperature},{humidity},{pressure}"); } } public class ForcastDisply : Observer { private Subject weatherData; public ForcastDisply(Subject weatherData) { this.weatherData = weatherData; weatherData.RegisterObserver(this); } public void Update(float temperature, float humidity, float pressure) { Console.WriteLine($"天氣預報布告板:{temperature},{humidity},{pressure}"); } }
對使用了觀察者模式的氣象站進行測試,當數據變化的時候就會自動通知觀察者並更新數據,也可以靈活的添加移除觀察者而不用去具體的實現里面修改代碼。
static void Main(string[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionDisply currentConditionDisply = new CurrentConditionDisply(weatherData); StaisticsDisply staisticsDisply = new StaisticsDisply(weatherData); ForcastDisply forcastDisply = new ForcastDisply(weatherData); weatherData.SetMeasurments(30, 65, 30.5F); Console.WriteLine("---------------移除訂閱者-----------"); weatherData.RemoveObserver(currentConditionDisply); weatherData.SetMeasurments(31,55,20); Console.WriteLine("---------------添加訂閱者-----------"); weatherData.RegisterObserver(currentConditionDisply); weatherData.SetMeasurments(30, 55, 30.5F); Console.ReadKey(); }