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
觀察者和被觀察者之間的互動關係不能是類之間的直接調用,那樣就將觀察者和被觀察對象緊密耦合起來了,從而違反了面向對象設計原則。編碼
經典觀察者模式類圖以下,在這個模型中,抽象的被被觀察者(主題ISubject)有註冊(attach)、取消註冊(detech)、通知(notify)方法。
因爲全部觀察者均抽象成了IObserver,從而解除了主題與具體觀察者之間的耦合。
spa
在事件處理場景,很天然會想到利用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
本文將基於一個經典案例分別用event 和IObserver 實現觀察者模式。
場景描述:
氣象部門根據氣象衛星獲取溫度信息,當溫度超過某一閾值時,須要向各單位發出高溫預警通知,以便其及時作好高溫防禦錯誤。
在這個場景中,發佈者是預警系統,觀察者是各個單位。
抽象的模型圖以下:
發佈者(主題)的定義:
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--; } }
在上面的代碼中進行改造
發佈者(主題)的定義:
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--; } }