在現(xiàn)實世界中,許多對象并不是獨立存在的,其中一個對象的行為發(fā)生改變可能會導致一個或者多個其他對象的行為也發(fā)生改變。例如,某種商品的物價上漲時會導致部分商家高興,而消費者傷心。
在軟件世界也是這樣,例如,事件模型中的事件源與事件處理者。所有這些,如果用觀察者模式來實現(xiàn)就非常方便。
觀察者(Observer)模式的定義:指多個對象間存在一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。這種模式有時又稱作發(fā)布-訂閱模式、模型-視圖模式,它是對象行為型模式。
觀察者模式是一種對象行為型模式,其主要優(yōu)點如下:
降低了目標與觀察者之間的耦合關(guān)系,兩者之間是抽象耦合關(guān)系。
目標與觀察者之間建立了一套觸發(fā)機制。
它的主要缺點如下:
目標與觀察者之間的依賴關(guān)系并沒有完全解除,而且有可能出現(xiàn)循環(huán)引用。
當觀察者對象很多時,通知的發(fā)布會花費很多時間,影響程序的效率。
實現(xiàn)觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調(diào)用,否則將使兩者之間緊密耦合起來,這違反了面向?qū)ο蟮脑O(shè)計原則。
觀察者模式的主要角色如下:
抽象主題(Subject)角色:也叫抽象目標類,它提供了一個用于保存觀察者對象的聚集類和增加、刪除觀察者對象的方法,以及通知所有觀察者的抽象方法。
具體主題(Concrete Subject)角色:也叫具體目標類,它實現(xiàn)抽象目標中的通知方法,當具體主題的內(nèi)部狀態(tài)發(fā)生改變時,通知所有注冊過的觀察者對象。
抽象觀察者(Observer)角色:它是一個抽象類或接口,它包含了一個更新自己的抽象方法,當接到具體主題的更改通知時被調(diào)用。
具體觀察者(Concrete Observer)角色:實現(xiàn)抽象觀察者中定義的抽象方法,以便在得到目標的更改通知時更新自身的狀態(tài)。
觀察者模式的結(jié)構(gòu)圖如圖所示:
觀察者模式的實現(xiàn)代碼如下:
class Program { static void Main(string[] args) { Subject subject=new ConcreteSubject(); IObserver obs1=new ConcreteObserver1(); IObserver obs2=new ConcreteObserver2(); subject.Add(obs1); subject.Add(obs2); subject.NotifyObserver(); Console.Read(); } } //抽象目標 public abstract class Subject { protected List<IObserver> observers=new List<IObserver>(); //增加觀察者方法 public void Add(IObserver observer) { observers.Add(observer); } //刪除觀察者方法 public void Remove(IObserver observer) { observers.Remove(observer); } public abstract void NotifyObserver(); //通知觀察者方法 } //具體目標 public class ConcreteSubject : Subject { public override void NotifyObserver() { Console.WriteLine("具體目標發(fā)生改變..."); Console.WriteLine("--------------"); foreach (var obs in observers) { obs.Response(); } } } //抽象觀察者 public interface IObserver { void Response(); //反應(yīng) } //具體觀察者1 public class ConcreteObserver1 : IObserver { public void Response() { Console.WriteLine("具體觀察者1作出反應(yīng)!"); } } //具體觀察者1 public class ConcreteObserver2 : IObserver { public void Response() { Console.WriteLine("具體觀察者2作出反應(yīng)!"); } }
程序運行結(jié)果如下:
具體目標發(fā)生改變... -------------- 具體觀察者1作出反應(yīng)! 具體觀察者2作出反應(yīng)!
通過前面的分析與應(yīng)用實例可知觀察者模式適合以下幾種情形:
對象間存在一對多關(guān)系,一個對象的狀態(tài)發(fā)生改變會影響其他對象。
當一個抽象模型有兩個方面,其中一個方面依賴于另一方面時,可將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。
在.net環(huán)境下,其運行時庫為開發(fā)者提供了IObservable和 IObserver接口,用于實現(xiàn)觀察者模式軟件設(shè)計。另外,ObservableCollection 類表示一個動態(tài)數(shù)據(jù)集合,它可在添加、刪除項目或刷新整個列表時提供通知。
提供者或主題,是將通知發(fā)送給觀察者的對象。 提供程序是實現(xiàn)IObservable接口的類或結(jié)構(gòu)。 提供者必須實現(xiàn)單個方法IObservable.Subscribe,該方法由希望從提供者接收通知的觀察者調(diào)用。
觀察者,即從提供程序接收通知的對象。 觀察者是實現(xiàn) IObserver接口的類或結(jié)構(gòu)。 觀察者必須實現(xiàn)以下三個方法,這三個方法均由提供程序調(diào)用:
IObserver.OnNext,它向觀察者提供新信息或當前信息。
IObserver.OnError,它通知觀察者已發(fā)生錯誤。
IObserver.OnCompleted,它指示提供程序已完成發(fā)送通知。允許提供程序跟蹤觀察者的一種機制。 通常情況下,提供程序使用容器對象(如 List對象)來保存對已訂閱通知的 IObserver實現(xiàn)的引用。 將存儲容器用于此目的使提供程序能夠處理零到無限數(shù)量的觀察者。 未定義觀察者接收通知的順序;提供程序可以隨意使用任何方法來確定順序。
IDisposable 實現(xiàn),它使提供程序在能夠通知完成時刪除觀察者。 觀察者從 Subscribe 方法接收對 IDisposable 實現(xiàn)的引用,因此它們還可以調(diào)用 IDisposable.Dispose 方法,以便在提供程序已完成發(fā)送通知之前取消訂閱。
包含提供程序發(fā)送到其觀察者的數(shù)據(jù)的對象。 此對象的類型對應(yīng) IObservable和 IObserver接口的泛型類型參數(shù)。 盡管此對象可與 IObservable
實現(xiàn)相同,但通常情況下,它是一個單獨的類型。
注:在 Java 中,通過 java.util.Observable 類和 java.util.Observer 接口定義了觀察者模式,只要實現(xiàn)它們的子類就可以編寫觀察者模式實例。
下面的示例演示觀察者設(shè)計模式,實現(xiàn)定位系統(tǒng)實時通知當前經(jīng)緯度坐標,代碼如下:
class Program { static void Main(string[] args) { // 定義一個提供者和兩個觀察者 LocationTracker provider = new LocationTracker(); LocationReporter reporter1 = new LocationReporter("FixedGPS"); reporter1.Subscribe(provider); LocationReporter reporter2 = new LocationReporter("MobileGPS"); reporter2.Subscribe(provider); provider.TrackLocation(new Location(47.6456, -122.1312)); reporter1.Unsubscribe(); provider.TrackLocation(new Location(47.6677, -122.1199)); provider.TrackLocation(null); provider.EndTransmission(); Console.Read(); } } /// <summary> /// 位置:包含緯度和經(jīng)度信息 /// </summary> public struct Location { double lat, lon; public Location(double latitude, double longitude) { this.lat = latitude; this.lon = longitude; } /// <summary> /// 緯度 /// </summary> public double Latitude { get { return this.lat; } } /// <summary> /// 經(jīng)度 /// </summary> public double Longitude { get { return this.lon; } } } /// <summary> /// 位置報告者:提供 IObserver<T> 實現(xiàn),它顯示有關(guān)當前控制臺位置的信息 /// </summary> public class LocationReporter : IObserver<Location> { private IDisposable unsubscriber; private string instName; public LocationReporter(string name) { this.instName = name; } public string Name { get { return this.instName; } } /// <summary> /// 訂閱:將由對 Subscribe 的調(diào)用返回的 IDisposable 實現(xiàn)保存到私有變量中 /// </summary> /// <param name="provider"></param> public virtual void Subscribe(IObservable<Location> provider) { if (provider != null) unsubscriber = provider.Subscribe(this); } public virtual void OnCompleted() { Console.WriteLine("位置跟蹤器已將數(shù)據(jù)傳輸?shù)?nbsp;{0}", this.Name); this.Unsubscribe(); } public virtual void OnError(Exception e) { Console.WriteLine("{0}: 無法確定位置", this.Name); } public virtual void OnNext(Location value) { Console.WriteLine("{2}: 當前位置是 {0}, {1}", value.Latitude, value.Longitude, this.Name); } /// <summary> /// 退訂:使類可以通過調(diào)用提供程序的 Dispose 實現(xiàn)來取消訂閱通知 /// </summary> public virtual void Unsubscribe() { unsubscriber.Dispose(); } } /// <summary> /// 位置跟蹤器:提供 IObservable<T> 實現(xiàn) /// </summary> public class LocationTracker : IObservable<Location> { public LocationTracker() { observers = new List<IObserver<Location>>(); } private List<IObserver<Location>> observers; /// <summary> /// 訂閱:某觀察程序?qū)⒁邮胀ㄖ? /// </summary> /// <param name="observer"></param> /// <returns></returns> public IDisposable Subscribe(IObserver<Location> observer) { if (!observers.Contains(observer)) observers.Add(observer); return new Unsubscriber(observers, observer); } /// <summary> /// IDisposable 實現(xiàn):用于刪除觀察者或取消訂閱 /// </summary> private class Unsubscriber : IDisposable { private List<IObserver<Location>> _observers; private IObserver<Location> _observer; public Unsubscriber(List<IObserver<Location>> observers, IObserver<Location> observer) { this._observers = observers; this._observer = observer; } public void Dispose() { if (_observer != null && _observers.Contains(_observer)) _observers.Remove(_observer); } } public void TrackLocation(Nullable<Location> loc) { foreach (var observer in observers) { if (!loc.HasValue) observer.OnError(new LocationUnknownException()); else observer.OnNext(loc.Value); } } public void EndTransmission() { foreach (var observer in observers.ToArray()) { if (observers.Contains(observer)) observer.OnCompleted(); } observers.Clear(); } } /// <summary> /// 位置未知異常 /// </summary> public class LocationUnknownException : Exception { internal LocationUnknownException() { } }
程序運行結(jié)果如下:
FixedGPS:當前位置是47.6456,-122.1312 MobileGPS:當前位置是47.6456,-122.1312 MobileGPS:當前位置是47.6677,-122.1199 MobileGPS:無法確定位置位置 跟蹤器已將數(shù)據(jù)傳輸?shù)組obileGPS