一 觀察者模式的定義和結構圖
在學過了策略模式後,讓我們來看一個更有趣的模式---觀察者模式,這可是Framework設計用到最多的模式啊。
首先讓我們來看觀察者模式(Observer Pattern)的定義:觀察者模式又叫做發佈-訂閲(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象的狀態改變時,它的所有依賴者都會收到通知並自動更新。觀察者模式的結構圖如下:
二 生活中的例子
讓我們來看一個觀察者模式在現實生活中的例子。
1) 報社的業務就是出版報紙,就相當於結構圖中所説的Subject.
2) 讀者向報社訂閲報紙,只要報社有新的報紙出版,就會送給所有的讀者。在這裏讀者就相當於結構圖中的Observer
3) 當某一個讀者不想再看報社的報紙的時候,就可以取消訂閲,這樣當報社再有新的報紙時,這個讀者就不會收到了。當然,讀者也可以在某一天重新訂閲報紙。
4) 只要報社還在運營,就不斷的會有讀者進行訂閲和退訂,否則報社就失去了存在的必要了。
三 一個實際應用觀察者模式的例子
讓我們來看一個天氣預報系統的例子,有一個WeatherData對象負責從氣象站得到氣象數據,我們要實現的功能就是建立三個氣象板,一個顯示當前氣象狀況,一個顯示氣象統計,一個顯示天氣預報。
既然本文講的是觀察者模式,那麼在這個例子中,我們當然要用觀察者模式來進行設計,那麼該如何來進行氣象程序的設計呢?別急,先讓我們來看一下程序的UML類圖
具體程序如下:
Codeusing System;
using System.Collections.ObjectModel;
public interface IObserver
{
void Update(float temprature, float humidity, float pressure);
} //end Iobserver
public interface ISubject
{
void NotifyObservers();
void RegisterObserver(IObserver observer);
void RemoveObserver(IObserver observer);
} //end Isubject
public interface IDisplayElement
{
void Display();
} //end IDisplayElement
public class WeatherData : ISubject
{
private float humidity;
private Collection<IObserver> observers;
private float pressure;
private float temprature;
public WeatherData()
{
observers = new Collection<IObserver>();
}
ISubject Members#region ISubject Members
public void NotifyObservers()
{
foreach(IObserver observer in observers)
{
observer.Update(this.temprature, this.humidity, this.pressure);
}
}
public void RegisterObserver(IObserver observer)
{
observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
observers.Remove(observer);
}
#endregion
public float GetHumidity()
{
return this.humidity;
}
public float GetPresure()
{
return this.pressure;
}
public float GetTemplature()
{
return this.temprature;
}
public void MeasurementsChanged()
{
NotifyObservers();
}
} //end WeatherData
public class CurrentConditionsDisplay : IObserver, IDisplayElement
{
private float humidity;
private float pressure;
private float temprature;
private ISubject weatherData;
public CurrentConditionsDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
this.weatherData.RegisterObserver(this);
}
IDisplayElement Members#region IDisplayElement Members
public void Display()
{
Console.WriteLine("現在的氣象情況是:温度{0},温度{1},壓力{2}",
this.temprature,
this.humidity,
this.pressure);
}
#endregion
IObserver Members#region IObserver Members
public void Update(float temprature, float humidity, float pressure)
{
this.temprature = temprature;
this.humidity = humidity;
this.pressure = pressure;
Display();
}
#endregion
} //end CurrentConditionsDisplay
public class ForecastDisplay : IDisplayElement, IObserver
{
private float humidity;
private float pressure;
private float temprature;
private ISubject weatherData;
public ForecastDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
this.weatherData.RegisterObserver(this);
}
IDisplayElement Members#region IDisplayElement Members
public void Display()
{
Console.WriteLine("未來的氣象情況可能是:温度{0},温度{1},壓力{2}",
this.temprature,
this.humidity,
this.pressure);
}
#endregion
IObserver Members#region IObserver Members
public void Update(float temprature, float humidity, float pressure)
{
this.temprature = temprature;
this.humidity = humidity;
this.pressure = pressure;
Display();
}
#endregion
} //end ForecastDisplay
public class StatisticsDisplay : IDisplayElement, IObserver
{
private float humidity;
private float pressure;
private float temprature;
private ISubject weatherData;
public StatisticsDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
this.weatherData.RegisterObserver(this);
}
IDisplayElement Members#region IDisplayElement Members
public void Display()
{
Console.WriteLine("最近的氣象統計是:温度{0},温度{1},壓力{2}",
this.temprature,
this.humidity,
this.pressure);
}
#endregion
IObserver Members#region IObserver Members
public void Update(float temprature, float humidity, float pressure)
{
this.temprature = temprature;
this.humidity = humidity;
this.pressure = pressure;
Display();
}
#endregion
} //end StatisticsDisplay
public class ThirdPartyDisplay : IDisplayElement, IObserver
{
private ISubject weatherData;
public ThirdPartyDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
weatherData.RegisterObserver(this);
}
IDisplayElement Members#region IDisplayElement Members
public void Display()
{
throw new NotImplementedException();
}
#endregion
IObserver Members#region IObserver Members
public void Update(float temprature, float humidity, float pressure)
{
throw new NotImplementedException();
}
#endregion
} //end ThirdPartyDisplay
四 什麼時候應該利用觀察者模式
觀察者模式主要用來解除一個抽象模型的緊耦合,當一個抽象模型的有兩個方面,其中一個方面(通常是多的那一方)依賴於另一個方面(通常是少的那一方)的變化,這時,我們應用觀察者模式,將多的一方定義為訂閲者,少的一方定義為發佈者。
發佈者不需要知道訂閲者的具體類型,也不需要知道到底會有多少訂閲者,只需要知道訂閲者實現了訂閲者接口,當發佈者的狀態變化時,就會通知當前所有的訂閲者。
訂閲者也很靈活,它只需要實現訂閲者接口,就可以根據需要決定是否訂閲發佈者的狀態,這點在運行時可變的(這是拜組合而賜,而不是我們常説的繼承啊!)。另外根據需要,訂閲者也可以選擇是“推模式”還是“拉模式”。
五 觀察者模式的優點和缺點:
我們來看呂震宇總結的觀察者模式的優點和缺點:
Observer模式的優點是實現了表示層和數據邏輯層的分離,並定義了穩定的更新消息傳遞機制,類別清晰,並抽象了更新接口,使得可以有各種各樣不同的表示層(觀察者)。
但是其缺點是每個外觀對象必須繼承這個抽像出來的接口類,這樣就造成了一些不方便,比如有一個別人寫的外觀對象,並沒有繼承該抽象類,或者接口不對,我們又希望不修改該類直接使用它。雖然可以再應用Adapter模式來一定程度上解決這個問題,但是會造成更加複雜煩瑣的設計,增加出錯機率。
觀察者模式的效果有以下幾個優點:
(1)觀察者模式在被觀察者和觀察者之間建立一個抽象的耦合。被觀察者角色所知道的只是一個具體現察者聚集,每一個具體現察者都符合一個抽象觀察者的接口。被觀察者並不認識任何一個具體觀察者,它只知道它們都有一個共同的接口。由於被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬於不同的抽象化層次。
(2)觀察者模式支持廣播通信。被觀察者會向所有的登記過的觀察者發出通知。
觀察者模式有下面的一些缺點:
(1)如果一個被觀察者對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
(2)如果在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,導致系統崩潰。在使用觀察考模式時要特別注意這一點。
(3)如果對觀察者的通知是通過另外的線程進行異步投遞的話,系統必須保證投遞是以自恰的方式進行的。(這點不是很明白,什麼叫自恰的方式?)
(4)雖然觀察者模式可以隨時使觀察者知道所觀察的對象發生了變化,但是觀察者模式沒有相應的機制使觀察者知道所觀察的對象是怎麼發生變化的