觀察者設計模式使訂閱者可以向提供程序註冊並接收相關通知。 它適合全部須要推送通知的方案。 該模式定義一個提供程序(也稱爲主題或觀察對象)以及零個、一個或多個觀察者。 觀察者向提供程序註冊,而且當任何預約義的條件、事件或狀態更改發生時,提供程序就會調用觀察者中的一種方法,自動通知全部觀察者。 在此方法調用中,提供程序還能夠向觀察者提供當前的狀態信息。 在 .NET Framework 中,經過實現泛型 System.IObservable<T> 和 System.IObserver<T> 接口來應用觀察者設計模式。 泛型類型參數表示提供通知信息的類型。編程
應用模式
觀察者設計模式適用於分佈式推送通知,由於它支持兩種不一樣的組件或應用程序層之間的絕對分離,例如數據源(業務邏輯)層和用戶界面(顯示)層。 每當提供程序使用回調向其客戶端提供當前信息時,便可實現該模式。設計模式
實現該模式須要您提供如下內容:promise
-
提供程序或主體,即將通知發送給觀察者的對象。 提供程序是一個實現 IObservable<T> 接口的類或結構。 提供程序必須實現一個方法 (IObservable<T>.Subscribe),但願接收提供程序通知的觀察者會調用該方法。安全
-
觀察者,即接收提供程序通知的對象。 觀察者是一個實現 IObserver<T> 接口的類或結構。 觀察者必須實現三個方法,提供程序將調用全部這些方法:多線程
-
IObserver<T>.OnNext ,向觀察者提供新信息或當前信息。併發
-
IObserver<T>.OnError ,通知觀察者發生錯誤。app
-
IObserver<T>.OnCompleted ,指示提供程序已完成通知發送。異步
-
-
容許提供程序跟蹤觀察者的機制。 一般狀況下,提供程序使用容器對象(例如 System.Collections.Generic.List<T> 對象)存放對已訂閱通知的 IObserver<T> 實現的引用。 所以目的而使用存儲容器時,提供程序可以處理零到無限數量的觀察者。 不定義觀察者接收通知的順序;提供程序能夠自由選擇任何方法來肯定該順序。async
-
容許提供程序在完成通知時移除觀察者的 IDisposable 實現。 觀察者從 Subscribe 方法接收對 IDisposable 實現的引用,因此它們還能夠調用 IDisposable.Dispose 方法以在提供程序完成通知發送以前取消訂閱。分佈式
-
包含提供程序向其觀察者發送的數據的對象。 該對象的類型對應於 IObservable<T> 和 IObserver<T> 接口的泛型類型參數。 儘管該對象能夠與 IObservable<T> 實現相同,但在一般狀況下,該對象爲不一樣的類型。
![]() |
---|
除實現觀察者設計模式外,您可能會對探索使用 IObservable<T> 和 IObserver<T> 接口構建的庫感興趣。 例如,Reactive Extensions for .NET (Rx) 包含一組支持異步編程的擴展方法和 LINQ 標準序列運算符。 |
實現模式
下面的示例使用觀察者設計模式實現機場行李提取信息系統。 BaggageInfo 類提供有關到達航班和提取各次航班行李所用轉盤的信息。 如如下示例中所示。
using System; using System.Collections.Generic; public class BaggageInfo { private int flightNo; private string origin; private int location; internal BaggageInfo(int flight, string from, int carousel) { this.flightNo = flight; this.origin = from; this.location = carousel; } public int FlightNumber { get { return this.flightNo; } } public string From { get { return this.origin; } } public int Carousel { get { return this.location; } } }
BaggageHandler 類負責接收有關到達航班和行李提取轉盤的信息。 在內部,它包含兩個集合:
-
observers - 接收更新信息的客戶端集合。
-
flights - 航班及其指派的轉盤的集合。
兩個集合都由 BaggageHandler 類構造函數中實例化的泛型 List<T> 對象表示。 下面的示例中顯示 BaggageHandler 類的源代碼。
public class BaggageHandler : IObservable<BaggageInfo> { private List<IObserver<BaggageInfo>> observers; private List<BaggageInfo> flights; public BaggageHandler() { observers = new List<IObserver<BaggageInfo>>(); flights = new List<BaggageInfo>(); } public IDisposable Subscribe(IObserver<BaggageInfo> observer) { // Check whether observer is already registered. If not, add it if (! observers.Contains(observer)) { observers.Add(observer); // Provide observer with existing data. foreach (var item in flights) observer.OnNext(item); } return new Unsubscriber<BaggageInfo>(observers, observer); } // Called to indicate all baggage is now unloaded. public void BaggageStatus(int flightNo) { BaggageStatus(flightNo, String.Empty, 0); } public void BaggageStatus(int flightNo, string from, int carousel) { var info = new BaggageInfo(flightNo, from, carousel); // Carousel is assigned, so add new info object to list. if (carousel > 0 && ! flights.Contains(info)) { flights.Add(info); foreach (var observer in observers) observer.OnNext(info); } else if (carousel == 0) { // Baggage claim for flight is done var flightsToRemove = new List<BaggageInfo>(); foreach (var flight in flights) { if (info.FlightNumber == flight.FlightNumber) { flightsToRemove.Add(flight); foreach (var observer in observers) observer.OnNext(info); } } foreach (var flightToRemove in flightsToRemove) flights.Remove(flightToRemove); flightsToRemove.Clear(); } } public void LastBaggageClaimed() { foreach (var observer in observers) observer.OnCompleted(); observers.Clear(); } }
但願接收更新信息的客戶端調用 BaggageInfo.Subscribe 方法。 若是客戶端以前沒有訂閱過通知,則會將對客戶端 IObserver<T> 實現的引用添加到 observers 集合中。
能夠調用重載的 BaggageHandler.BaggageStatus 方法以指示是正在卸載仍是已卸載航班行李。 在第一種狀況中,向該方法傳遞航班號、航班起飛的機場,以及卸載行李的轉盤。 在第二種狀況中,僅向該方法傳遞航班號。 對於正在卸載的行李,該方法檢查傳遞給方法的 BaggageInfo 信息是否位於 flights 集合中。 若是沒有,該方法將添加相應信息並調用每一個觀察者的 OnNext 方法。 對於完成行李卸載的航班,該方法會檢查有關此航班的信息是否位於 flights 集合中。 若是在該集合中,該方法會調用每一個觀察者的 OnNext 方法並從 flights 集合中移除 BaggageInfo 對象。
當天最後一趟航班着陸並處理完其行李後,將調用 BaggageHandler.LastBaggageClaimed 方法。 該方法調用每一個觀察者的 OnCompleted 方法以指示已完成全部通知,而後清除 observers 集合。
提供程序的 Subscribe 方法返回使觀察者能夠在調用 OnCompleted 方法以前中止接收通知的 IDisposable 實現。 下面的示例中顯示該 Unsubscriber(Of BaggageInfo) 類的源代碼。 當類在 BaggageHandler.Subscribe 方法中實例化時,將向其傳遞對 observers 集合的引用以及對添加至該集合的觀察者的引用。 這些引用將指派給局部變量。 當調用對象的 Dispose 方法時,它會檢查觀察者是否仍在 observers 集合中,若是在,則移除觀察者。
internal class Unsubscriber<BaggageInfo> : IDisposable { private List<IObserver<BaggageInfo>> _observers; private IObserver<BaggageInfo> _observer; internal Unsubscriber(List<IObserver<BaggageInfo>> observers, IObserver<BaggageInfo> observer) { this._observers = observers; this._observer = observer; } public void Dispose() { if (_observers.Contains(_observer)) _observers.Remove(_observer); } }
下面的示例提供名爲 ArrivalsMonitor 的 IObserver<T> 實現,它是顯示行李提取信息的基類。 該信息按起飛城市名稱的字母順序顯示。 ArrivalsMonitor 的方法標記爲 overridable (Visual Basic) 或 virtual (C#),因此它們可所有由派生類重寫。
using System; using System.Collections.Generic; public class ArrivalsMonitor : IObserver<BaggageInfo> { private string name; private List<string> flightInfos = new List<string>(); private IDisposable cancellation; private string fmt = "{0,-20} {1,5} {2, 3}"; public ArrivalsMonitor(string name) { if (String.IsNullOrEmpty(name)) throw new ArgumentNullException("The observer must be assigned a name."); this.name = name; } public virtual void Subscribe(BaggageHandler provider) { cancellation = provider.Subscribe(this); } public virtual void Unsubscribe() { cancellation.Dispose(); flightInfos.Clear(); } public virtual void OnCompleted() { flightInfos.Clear(); } // No implementation needed: Method is not called by the BaggageHandler class. public virtual void OnError(Exception e) { // No implementation. } // Update information. public virtual void OnNext(BaggageInfo info) { bool updated = false; // Flight has unloaded its baggage; remove from the monitor. if (info.Carousel == 0) { var flightsToRemove = new List<string>(); string flightNo = String.Format("{0,5}", info.FlightNumber); foreach (var flightInfo in flightInfos) { if (flightInfo.Substring(21, 5).Equals(flightNo)) { flightsToRemove.Add(flightInfo); updated = true; } } foreach (var flightToRemove in flightsToRemove) flightInfos.Remove(flightToRemove); flightsToRemove.Clear(); } else { // Add flight if it does not exist in the collection. string flightInfo = String.Format(fmt, info.From, info.FlightNumber, info.Carousel); if (! flightInfos.Contains(flightInfo)) { flightInfos.Add(flightInfo); updated = true; } } if (updated) { flightInfos.Sort(); Console.WriteLine("Arrivals information from {0}", this.name); foreach (var flightInfo in flightInfos) Console.WriteLine(flightInfo); Console.WriteLine(); } } }
ArrivalsMonitor 類包括 Subscribe 和 Unsubscribe 方法。 Subscribe 方法容許類將由對 Subscribe 的調用返回的 IDisposable 實現保存至私有變量中。 Unsubscribe 方法容許類經過調用提供程序的 Dispose 實現來取消訂閱通知。 ArrivalsMonitor 還提供 OnNext、OnError 和 OnCompleted 方法的實現。 僅 OnNext 實現包含大量代碼。 該方法使用已排序的私有泛型 List<T> 對象,此對象維護到達航班的起飛機場及其行李使用的轉盤的信息。 若是 BaggageHandler 類報告有新航班到達,則 OnNext 方法實現會將有關該航班的信息添加到列表中。 若是 BaggageHandler 類報告已卸載航班行李,則 OnNext 方法會從列表中移除該航班。 不管什麼時候進行更改,都會對列表進行排序,並將其顯示在控制檯上。
下面的示例包含實例化 BaggageHandler 類的應用程序入口點,以及兩個 ArrivalsMonitor 類的實例,並使用 BaggageHandler.BaggageStatus 方法添加和移除有關到達航班的信息。 在每種狀況下,觀察者都接收更新並正確顯示行李提取信息。
using System; using System.Collections.Generic; public class Example { public static void Main() { BaggageHandler provider = new BaggageHandler(); ArrivalsMonitor observer1 = new ArrivalsMonitor("BaggageClaimMonitor1"); ArrivalsMonitor observer2 = new ArrivalsMonitor("SecurityExit"); provider.BaggageStatus(712, "Detroit", 3); observer1.Subscribe(provider); provider.BaggageStatus(712, "Kalamazoo", 3); provider.BaggageStatus(400, "New York-Kennedy", 1); provider.BaggageStatus(712, "Detroit", 3); observer2.Subscribe(provider); provider.BaggageStatus(511, "San Francisco", 2); provider.BaggageStatus(712); observer2.Unsubscribe(); provider.BaggageStatus(400); provider.LastBaggageClaimed(); } } // The example displays the following output: // Arrivals information from BaggageClaimMonitor1 // Detroit 712 3 // // Arrivals information from BaggageClaimMonitor1 // Detroit 712 3 // Kalamazoo 712 3 // // Arrivals information from BaggageClaimMonitor1 // Detroit 712 3 // Kalamazoo 712 3 // New York-Kennedy 400 1 // // Arrivals information from SecurityExit // Detroit 712 3 // // Arrivals information from SecurityExit // Detroit 712 3 // Kalamazoo 712 3 // // Arrivals information from SecurityExit // Detroit 712 3 // Kalamazoo 712 3 // New York-Kennedy 400 1 // // Arrivals information from BaggageClaimMonitor1 // Detroit 712 3 // Kalamazoo 712 3 // New York-Kennedy 400 1 // San Francisco 511 2 // // Arrivals information from SecurityExit // Detroit 712 3 // Kalamazoo 712 3 // New York-Kennedy 400 1 // San Francisco 511 2 // // Arrivals information from BaggageClaimMonitor1 // New York-Kennedy 400 1 // San Francisco 511 2 // // Arrivals information from SecurityExit // New York-Kennedy 400 1 // San Francisco 511 2 // // Arrivals information from BaggageClaimMonitor1 // San Francisco 511 2