在現實世界中,許多對象並不是獨立存在的,其中一個對象的行為發生改變可能會導致一個或者多個其他對象的行為也發生改變。例如,某種商品的物價上漲時會導致部分商家高興,而消費者傷心。
在軟件世界也是這樣,例如,事件模型中的事件源與事件處理者。所有這些,如果用觀察者模式來實現就非常方便。
定義與特點
觀察者(Observer)模式的定義:指多個對象間存在一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。這種模式有時又稱作發布-訂閱模式、模型-視圖模式,它是對象行為型模式。
觀察者模式是一種對象行為型模式,其主要優點如下:
- 降低了目標與觀察者之間的耦合關系,兩者之間是抽象耦合關系。
- 目標與觀察者之間建立了一套觸發機制。
它的主要缺點如下:
- 目標與觀察者之間的依賴關系並沒有完全解除,而且有可能出現循環引用。
- 當觀察者對象很多時,通知的發布會花費很多時間,影響程序的效率。
結構與實現
實現觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調用,否則將使兩者之間緊密耦合起來,這違反了面向對象的設計原則。
模式的結構
觀察者模式的主要角色如下:
- 抽象主題(Subject)角色:也叫抽象目標類,它提供了一個用於保存觀察者對象的聚集類和增加、刪除觀察者對象的方法,以及通知所有觀察者的抽象方法。
- 具體主題(Concrete Subject)角色:也叫具體目標類,它實現抽象目標中的通知方法,當具體主題的內部狀態發生改變時,通知所有注冊過的觀察者對象。
- 抽象觀察者(Observer)角色:它是一個抽象類或接口,它包含了一個更新自己的抽象方法,當接到具體主題的更改通知時被調用。
- 具體觀察者(Concrete Observer)角色:實現抽象觀察者中定義的抽象方法,以便在得到目標的更改通知時更新自身的狀態。
觀察者模式的結構圖如圖所示:
模式的實現
觀察者模式的實現代碼如下:
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("具體目標發生改變...");
Console.WriteLine("--------------");
foreach (var obs in observers)
{
obs.Response();
}
}
}
//抽象觀察者
public interface IObserver
{
void Response(); //反應
}
//具體觀察者1
public class ConcreteObserver1 : IObserver
{
public void Response()
{
Console.WriteLine("具體觀察者1作出反應!");
}
}
//具體觀察者1
public class ConcreteObserver2 : IObserver
{
public void Response()
{
Console.WriteLine("具體觀察者2作出反應!");
}
}
程序運行結果如下:
具體目標發生改變...
--------------
具體觀察者1作出反應!
具體觀察者2作出反應!
應用場景
通過前面的分析與應用實例可知觀察者模式適合以下幾種情形:
- 對象間存在一對多關系,一個對象的狀態發生改變會影響其他對象。
- 當一個抽象模型有兩個方面,其中一個方面依賴於另一方面時,可將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。
擴展:.net中的IObservable
和 IObserver
接口
在.net環境下,其運行時庫為開發者提供了IObservable
- 提供者或主題,是將通知發送給觀察者的對象。 提供程序是實現IObservable
接口的類或結構 。 提供者必須實現單個方法IObservable.Subscribe ,該方法由希望從提供者接收通知的觀察者調用。- 觀察者,即從提供程序接收通知的對象。 觀察者是實現 IObserver
接口的類或結構 。 觀察者必須實現以下三個方法,這三個方法均由提供程序調用:
IObserver.OnNext ,它向觀察者提供新信息或當前信息。
IObserver.OnError ,它通知觀察者已發生錯誤。
IObserver.OnCompleted ,它指示提供程序已完成發送通知。- 允許提供程序跟蹤觀察者的一種機制。 通常情況下,提供程序使用容器對象(如 List
對象)來保存對已訂閱通知的 IObserver 。 將存儲容器用於此目的使提供程序能夠處理零到無限數量的觀察者。 未定義觀察者接收通知的順序;提供程序可以隨意使用任何方法來確定順序。實現的引用 - IDisposable 實現,它使提供程序在能夠通知完成時刪除觀察者。 觀察者從 Subscribe 方法接收對 IDisposable 實現的引用,因此它們還可以調用 IDisposable.Dispose 方法,以便在提供程序已完成發送通知之前取消訂閱。
- 包含提供程序發送到其觀察者的數據的對象。 此對象的類型對應 IObservable
和 IObserver 。 盡管此對象可與 IObservable接口的泛型類型參數 實現相同,但 通常情況下,它是一個單獨的類型。
注:在 Java 中,通過 java.util.Observable 類和 java.util.Observer 接口定義了觀察者模式,只要實現它們的子類就可以編寫觀察者模式實例。
下面的示例演示觀察者設計模式,實現定位系統實時通知當前經緯度坐標,代碼如下:
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>
/// 位置:包含緯度和經度信息
/// </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>
/// 經度
/// </summary>
public double Longitude
{ get { return this.lon; } }
}
/// <summary>
/// 位置報告者:提供 IObserver<T> 實現,它顯示有關當前控制台位置的信息
/// </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 的調用返回的 IDisposable 實現保存到私有變量中
/// </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("位置跟蹤器已將數據傳輸到 {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>
/// 退訂:使類可以通過調用提供程序的 Dispose 實現來取消訂閱通知
/// </summary>
public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}
}
/// <summary>
/// 位置跟蹤器:提供 IObservable<T> 實現
/// </summary>
public class LocationTracker : IObservable<Location>
{
public LocationTracker()
{
observers = new List<IObserver<Location>>();
}
private List<IObserver<Location>> observers;
/// <summary>
/// 訂閱:某觀察程序將要接收通知
/// </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 實現:用於刪除觀察者或取消訂閱
/// </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()
{ }
}
程序運行結果如下:
FixedGPS:當前位置是47.6456,-122.1312
MobileGPS:當前位置是47.6456,-122.1312
MobileGPS:當前位置是47.6677,-122.1199
MobileGPS:無法確定位置位置
跟蹤器已將數據傳輸到MobileGPS
參考資料:
觀察者設計模式——MSDN
ObservableCollection
IObservable
IObserver