行爲型模式-觀察者模式的實現(C#)

1. 定義

Define a one-to-many dependency between objects so that when oneobject changes state, all its dependents are notified and updatedautomatically..編程

— Design Patterns : Elements of Reusable Object-Oriented Software
觀察者模式(Observer Pattern),又稱爲發佈/訂閱模式,它是軟件設計模式中的一種。觀察者模式定義了對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新。設計模式

在觀察者模式中,一個目標物件(被觀察者)管理全部依賴於他的觀察者,而且在它自己的狀態發生改變時主動發出通知,這一般經過呼叫各個觀察者所提供的方法來實現。dom

這種模式一般被用來實現事件處理系統。性能

觀察者模式有不少實現方式,從根本上說,該模式必須包含兩個角色:觀察者和被觀察者。this

觀察者和被觀察者之間的互動關係不能是類之間的直接調用,那樣就將觀察者和被觀察對象緊密耦合起來了,從而違反了面向對象設計原則。編碼

1.1 建模

經典觀察者模式類圖以下,在這個模型中,抽象的被被觀察者(主題ISubject)有註冊(attach)、取消註冊(detech)、通知(notify)方法。
因爲全部觀察者均抽象成了IObserver,從而解除了主題與具體觀察者之間的耦合。
spa

2.C# 觀察者的實現模式

在事件處理場景,很天然會想到利用event 關鍵字 和EventHandler來實現觀察者模式,這是一種最簡單的方法。
同時C#也提供了一種低級抽象IObserver--IObservable(System命名空間,來自System.Runtime.dll) 來實現觀察者。
被觀察者(主題)接口:設計

namespace System
{
    public interface IObservable<out T>
    {       
        IDisposable Subscribe(IObserver<T> observer);
    }
}

觀察者接口:code

namespace System
{
   
    public interface IObserver<in T>
    {
        void OnCompleted();
        void OnError(Exception error);
        void OnNext(T value);
    }
}

上述接口中,由被觀察者將觀察者註冊到觀察者列表,觀察者分別有完成、失敗、執行的動做。server

3. 案例

本文將基於一個經典案例分別用event 和IObserver 實現觀察者模式。
場景描述:
氣象部門根據氣象衛星獲取溫度信息,當溫度超過某一閾值時,須要向各單位發出高溫預警通知,以便其及時作好高溫防禦錯誤。

在這個場景中,發佈者是預警系統,觀察者是各個單位。

抽象的模型圖以下:

3.1 基於event 實現 觀察者

發佈者(主題)的定義:

public class Subject : IObservable<decimal>
{
        /// <summary>
        /// 觀察者處理委託
        /// </summary>
        public event EventHandler<decimal> Observers;

        /*
         * 高溫黃色預警 >=35,<37 
         * 高溫橙色預警 >=37,<40 
         * 高溫紅色預警 >=40    
         * **/
        public void SetTemperature(decimal temperature)
        {

            if (temperature >= 35)
            {
                if (temperature >= 40)
                {
                    _warningLevel = "紅色";
                }
                else if (temperature >= 37)
                {
                    _warningLevel = "橙色";
                }

                PublishWarning(temperature, _warningLevel);
            }
        }
        
        private void PublishWarning(decimal temperature, string warningLevel)
        {
            Console.WriteLine($"===========氣象部門發佈高溫{_warningLevel} 預警,氣溫:{temperature} ");
            
            Observers?.Invoke(this, temperature);
        }
      
}

分別定義三個觀察者

/// <summary>
    /// 企業單位觀察者
    /// </summary>
  public class EnterpriseObserver 
  {
        public void OnWarning(object sender,decimal eventArgs)
        {
            Console.WriteLine($"企業單位收到預警事件,氣溫:{eventArgs}");
        }
  }

    /// <summary>
    /// 政府部門觀察者
    /// </summary>
  public class EnterpriseObserver 
  {
        public void OnWarning(object sender, decimal eventArgs)
        {
            Console.WriteLine($"政府部門收到預警事件,氣溫:{eventArgs}");
        }
  }

    /// <summary>
    /// 我的觀察者
    /// </summary>
  public class EnterpriseObserver 
  {
        public void OnWarning(object sender, decimal eventArgs)
        {
            Console.WriteLine($"我的收到預警事件,氣溫:{eventArgs}");
        }
  }

客戶端調用,利用委託能夠多播的特性增長多個觀察者:

var subject = new Subject();

            subject.Observers += new EnterpriseObserver().OnWarning;
            subject.Observers += new GovernmentObserver().OnWarning;
            subject.Observers += new PersonObserver().OnWarning;
            int t = 3;
            var random = new Random(500);
            while (t > 0)
            {

                var temperature = random.NextDouble() * 50;
                if (temperature > 35)
                {
                    subject.SetTemperature((decimal)temperature);

                    t--;
                }
            }
  • 調用結果

3.2 基於IObserver--IObservable 實現觀察者

在上面的代碼中進行改造
發佈者(主題)的定義:

public class Unsubscriber<T> : IDisposable
    {
        private List<IObserver<T>> _observers;
        private IObserver<T> _observer;
        public Unsubscriber(List<IObserver<T>> observers,
            IObserver<T> observer)
        {
            _observers = observers;
            _observer = observer;
        }
        public void Dispose()
        {
            Console.WriteLine("Unsubscribed....");
            _observers.Remove(_observer);
        }
    }

    /// <summary>
    /// 發佈者
    /// </summary>
    public class Subject : IObservable<decimal>
    {
        /// <summary>
        /// 觀察者列表
        /// </summary>
        private List<IObserver<decimal>> observers;

        /// <summary>
        /// 觀察者處理委託
        /// </summary>
        public event EventHandler<decimal> Observers;

        private decimal _temperature;
        private string _warningLevel;


        public Subject()
        {
            observers = new List<IObserver<decimal>>();
        }

        public IDisposable Subscribe(IObserver<decimal> observer)
        {
            if (!observers.Contains(observer))
                observers.Add(observer);
            return new Unsubscriber<decimal>(observers, observer);
        }


        /*
         * 高溫黃色預警 >=35,<37 C
         * 高溫橙色預警 >=37,<40 C
         * 高溫紅色預警 >=40    C
         * **/
        public void SetTemperature(decimal temperature)
        {

            if (temperature >= 35)
            {
                if (temperature >= 40)
                {
                    _warningLevel = "紅色";
                }
                else if (temperature >= 37)
                {
                    _warningLevel = "橙色";
                }

                PublishWarning(temperature, _warningLevel);
            }
        }

        private void PublishWarning(decimal temperature, string warningLevel)
        {
            Console.WriteLine($"===========氣象部門發佈高溫{_warningLevel} 預警,氣溫:{temperature} ");
            foreach (var observer in observers)
            {
                observer.OnNext(temperature);
                
            }
            Observers?.Invoke(this, temperature);
        }
    }

觀察者定義:

/// <summary>
    /// 企業單位觀察者
    /// </summary>
    public class EnterpriseObserver : IObserver<decimal>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(decimal value)
        {
            Console.WriteLine($"企業單位收到預警信息,氣溫:{value}");
        }

        public void OnWarning(object sender,decimal eventArgs)
        {
            Console.WriteLine($"企業單位收到預警事件,氣溫:{eventArgs}");
        }
    }

    /// <summary>
    /// 政府部門
    /// </summary>
    public class GovernmentObserver : IObserver<decimal>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(decimal value)
        {
            Console.WriteLine($"政府部門收到預警信息,氣溫:{value}");
        }
        public void OnWarning(object sender, decimal eventArgs)
        {
            Console.WriteLine($"政府部門收到預警事件,氣溫:{eventArgs}");
        }
    }

    /// <summary>
    /// 我的觀察者
    /// </summary>
    public class PersonObserver : IObserver<decimal>
    {
        public void OnCompleted()
        {
        }

        public void OnError(Exception error)
        {
        }

        public void OnNext(decimal value)
        {
            Console.WriteLine($"我的收到預警信息,氣溫:{value}");
        }

        public void OnWarning(object sender, decimal eventArgs)
        {
            Console.WriteLine($"我的收到預警事件,氣溫:{eventArgs}");
        }
    }

客戶端調用:

var subject = new Subject();

            var ob1 = new EnterpriseObserver();
            var ob2 = new GovernmentObserver();
            var ob3 = new PersonObserver();

            subject.Subscribe(ob1);
            subject.Subscribe(ob2);
            subject.Subscribe(ob3);
            int t = 3;
            var random = new Random(500);
            while (t > 0)
            {
               
                var temperature = random.NextDouble()*50;
                if (temperature > 35)
                {
                    subject.SetTemperature((decimal)temperature);

                    t--;
                }
            }

4. 小結

  • 用C#實現觀察者模式時,基本不須要本身定義發佈者和觀察者的抽象,基礎類庫中已經提供了現成的。
  • 通常狀況下,使用event 編程模型實現觀察者模式更加精簡,編碼少。
  • 僅在一些對性能要求比較高的特殊場景,咱們能夠直接使用IObserver-IObservable接口實現觀察者模式,以減小event實例化的開銷,減輕GC壓力。
相關文章
相關標籤/搜索