1. 程式人生 > >設計模式學習(二)“觀察者模式” (C#)

設計模式學習(二)“觀察者模式” (C#)

original pan 學習筆記 pri 接口 program date contain 兩個

《深入淺出設計模式》學習筆記第二章

需求:

開發一套氣象監測應用,如圖:

技術分享

氣象站,目前有三種裝置,溫度、濕度和氣壓感應裝置。

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#)