設計模式學習(二)“觀察者模式” (C#)
《深入淺出設計模式》學習筆記第二章
需求:
開發一套氣象監測應用,如圖:
氣象站,目前有三種裝置,溫度、濕度和氣壓感應裝置。
WeatherData對象追蹤氣象站的數據,並更新到布告板,布告板(目前是三個:目前狀況、氣象統計、天氣預報)用來顯示目前的天氣狀況給用戶。
初步設計
目前的要求:
1.其中有三個方法分別獲得氣溫、濕度和氣壓的數據。
2.一旦氣象測量被更新,那麽這個measurementsChanged()方法就會被調用。
3.一旦有新的數據,者三個布告板(暫時三個)就會馬上更新。
4.可擴展,可以開發第三方的布告板。
錯誤的實現:
錯誤在哪:
1.變化的地方需要封裝。
2.布告板應該統一實現某個帶有update方法的接口。
3.不應該針對實現編程,應該針對接口編程。
什麽是觀察者模式 Observer Pattern
例子:
我們訂閱公眾號,公眾號一旦有新文章就會發送給我們。
當我不再想看文章時,就取消訂閱,這時就不會給我發送文章了。
只要公眾號還在運營,就一直有人訂閱或者取消訂閱。
出版者(Publishers) + 訂閱者(Subscribers) = 觀察者模式(Observer Pattern)
不過我們用的名詞不一樣,出版者改為主題(Subject),訂閱者改為觀察者(Observer)
觀察者模式定義:
觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者(dependents)都會收到通知並自動更新。
觀察這模式關系圖:
松耦合
兩個對象之間的松耦合就是,他們可以交互,但是不清楚對方太多細節。
觀察者模式的Subject與Observers之間是松耦合,因為:
1.Subject對於Observer知道的唯一一件事情就是它實現了某個接口。
2.隨時可以添加新的Observer。
3.新的Observer出現時,永遠不需要更改Subject的代碼。
4.我們可以獨立的復用Subject或Observer。
5.改變任意的一方並不會影響另外一方。
設計原則
交互對象之間應該盡可能的去實現松耦合設計。
氣象應用的具體設計:
C#實現:
接口們:
namespace C02ObserverPattern.Raw.Bases {public interface ISubject { // 訂閱 void RegisterObserver(IObserver observer); // 取消訂閱 void RemoveObserver(IObserver observer); // 狀態變化時,通知所有觀察者 void NotifyObservers(); } public interface IObserver { // 氣象之變化時,subject會把這些值更新給observers void Update(float temp, float humidity, float pressure); } public interface IDisplayElement { // 布告板顯示 void Display(); } }
WeatherData(subject):
namespace C02ObserverPattern.Raw { public class WeatherData: ISubject { private readonly HashSet<IObserver> _observers; private float _temp, _humidity, _pressure; public WeatherData() { _observers = new HashSet<IObserver>(); } public void RegisterObserver(IObserver observer) { _observers.Add(observer); } public void RemoveObserver(IObserver observer) { _observers.Remove(observer); } public void NotifyObservers() { foreach (var observer in _observers) { observer.Update(_temp, _humidity, _pressure); } } public void MeasurementsChanged() { NotifyObservers(); } public void SetMeasurements(float temp, float humidity, float pressure) { _temp = temp; _humidity = humidity; _pressure = pressure; MeasurementsChanged(); } } }
其中一個布告板:
namespace C02ObserverPattern.Raw { public class CurrentConditionDisplay: IObserver, IDisplayElement { private float _temp, _humidity; public CurrentConditionDisplay(ISubject weatherData) { weatherData.RegisterObserver(this); } public void Update(float temp, float humidity, float pressure) { _temp = temp; _humidity = humidity; Display(); } // 這個布告板只顯示溫度和濕度 public void Display() { Console.WriteLine($"Current conditions:{_temp}℃ and {_humidity}% humidity"); } } }
測試程序:
namespace C02ObserverPattern { class Program { static void Main(string[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); // XxxDisplay1 xxxDisplay1 = new XxxDisplay1(weatherData); // XxxDisplay2 xxxDisplay2 = new XxxDisplay2(weatherData); weatherData.SetMeasurements(10, 20, 30); weatherData.SetMeasurements(14, 25, 36); weatherData.SetMeasurements(40, 50, 60); Console.ReadLine(); } } }
結果:
----------------------------------------------------------------------------------------------------------------------------------------------------------------
使用C#內置的Observer Pattern
IObservable<T> 作為Subject。
IObserver<T> 作為Observer。
先封裝一下數據:
namespace C02ObserverPattern.BuiltIn { public struct MyData { public float Temperature { get; } public float Humidity { get; } public float Pressure { get; } public MyData(float temperature, float humidity, float pressure) { Temperature = temperature; Humidity = humidity; Pressure = pressure; } } }
WeatherData:
namespace C02ObserverPattern.BuiltIn { public class WeatherDataAlternative : IObservable<MyData> { private readonly HashSet<IObserver<MyData>> _observers; public WeatherDataAlternative() { _observers = new HashSet<IObserver<MyData>>(); } // 訂閱用戶,並返回可取消訂閱的對象 public IDisposable Subscribe(IObserver<MyData> observer) { if (!_observers.Contains(observer)) { _observers.Add(observer); } return new Unsubscriber(_observers, observer); } // 發布通知 public void NotifyMeasurementsChanged(MyData? data) { foreach (var observer in _observers) { if (!data.HasValue) { observer.OnError(new Exception("No value!!!")); } else { observer.OnNext(data.Value); } } } // 關閉這個Subject public void WeatherStationClose() { foreach (var observer in _observers.ToArray()) { observer.OnCompleted(); } _observers.Clear(); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("The weather station has been closed."); } // 取消訂閱的類 private class Unsubscriber : IDisposable { private readonly HashSet<IObserver<MyData>> _observers; private readonly IObserver<MyData> _observer; public Unsubscriber(HashSet<IObserver<MyData>> observers, IObserver<MyData> observer) { _observers = observers; _observer = observer; } public void Dispose() { if (_observer != null && _observers.Contains(_observer)) _observers.Remove(_observer); } } } }
實現兩個布告板:
namespace C02ObserverPattern.BuiltIn { public class CurrentConditionDisplayAlternative : IObserver<MyData> { private IDisposable _unsubscriber; // 訂閱 public virtual void Subscribe(IObservable<MyData> provider) { if (provider != null) _unsubscriber = provider.Subscribe(this); } // 相當於Display() public void OnNext(MyData value) { Console.WriteLine($"Current condition: {value.Temperature}C degrees and {value.Humidity}% humidity and {value.Pressure} pressure."); } // 發生錯誤時調用 public void OnError(Exception error) { var original = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Error occurred at Current Condition Display!"); Console.ForegroundColor = original; } // 完成時,一般就是取消訂閱 public void OnCompleted() { Console.WriteLine("Current condition display Task Completed."); Unsubscribe(); } // 取消訂閱 public virtual void Unsubscribe() { Console.WriteLine("Current condition display will unsubscribe from weather station."); _unsubscriber.Dispose(); } } }
namespace C02ObserverPattern.BuiltIn { public class StatisticsDisplayAlternative : IObserver<MyData> { private IDisposable _unsubscriber; public virtual void Subscribe(IObservable<MyData> provider) { if (provider != null) _unsubscriber = provider.Subscribe(this); } public void OnNext(MyData value) { Console.WriteLine($"Statistics: {value.Temperature}, {value.Humidity}, {value.Pressure}."); } public void OnError(Exception error) { var original = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("Error occurred at Statistics!"); Console.ForegroundColor = original; } public void OnCompleted() { Console.WriteLine("Statistics Task Completed."); Unsubscribe(); } public virtual void Unsubscribe() { Console.WriteLine("Statistics will unsubscribe from weather station."); _unsubscriber.Dispose(); } } }
測試程序:
namespace C02ObserverPattern { class Program { static void Main(string[] args) { WeatherDataAlternative weatherDataAlternative = new WeatherDataAlternative(); CurrentConditionDisplayAlternative currentConditionDisplayAlternative = new CurrentConditionDisplayAlternative(); currentConditionDisplayAlternative.Subscribe(weatherDataAlternative); StatisticsDisplayAlternative statisticsDisplayAlternative = new StatisticsDisplayAlternative(); statisticsDisplayAlternative.Subscribe(weatherDataAlternative); weatherDataAlternative.NotifyMeasurementsChanged(new MyData(25, 75, 120)); Task.Delay(1000); weatherDataAlternative.NotifyMeasurementsChanged(null); Task.Delay(1000); currentConditionDisplayAlternative.Unsubscribe(); weatherDataAlternative.NotifyMeasurementsChanged(new MyData(23, 45, 104)); weatherDataAlternative.WeatherStationClose(); Console.ReadLine(); } } }
設計模式學習(二)“觀察者模式” (C#)