觀察者模式:定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新(是不是感覺很神奇!!!)。
小A公司接了一個為氣象站開發Internet氣象觀測站的項目。項目開發要求:由氣象站提供一個WeatherData對象負責追蹤目前的天氣狀況(溫度、濕度、氣壓),要求公司開發一個應用,有三種布告板,分別及時更新顯示目前的狀況,而且這個應用能夠易於擴展開發新的布告板。
現在我們開始開發吧
1.第一次嘗試
class WeatherData { public void measurementsChanged() {
//取得最新測量值 float temp = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); //更新到各個布告板 CurrentConditionsDisplay.update(temp, humidity, pressure); statisticsDisplay.update(temp, humidity, pressure); forecastDisplay.update(temp, humidity, pressure); } //這里是其他WeatherData方法 }
解析下:代碼看上去蠻漂亮的,但是想想我們的設計原則
1.找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。
2.針對接口編程,而不是針對實現編程。
2.第二次嘗試:觀察者模式
觀察者模式主要的兩個對象是主題和觀察者,觀察者依賴於此主題,只要主題狀態一有變化,觀察者就會被通知。根據通知的風格觀察者可能因此新值而更新。
接下來我們來看下觀察者模式的一種常見做法:Subject和Observer接口的類設計的做法。
public interface Subject { void registerObserver(Observer o);//以觀察者作為變量,該觀察者是用來注冊的。 void removeObserver(Observer o);//以觀察者作為變量,該觀察者是用來刪除的。 void notifyObservers();//當主題狀態改變時,這個方法會被調用,以通知所有觀察者。 }
public interface Observer { void update(float temp,float humidity,float pressure);//所有觀察者都必須實現updata()方法,以實現觀察者接口。 }
class WeatherData:Subject {
//ArrayList 來記錄依賴主題的所有觀察者 private ArrayList observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList(); } #region Subject 成員 //當注冊觀察者時,我們只要把它加到ArrayList的后面即可。 public void registerObserver(Observer o) { observers.Add(o ); } //當刪除觀察者時,我們只要把它從ArrayList中刪除即可。 public void removeObserver(Observer o) { int i = observers.IndexOf(o ); if (i >=0) { observers.Remove(i ); } } //有趣的地方來了!在這里,我們把狀態告訴每一個觀察者。
//因為觀察者都實現了update(),所以我們知道如何通知它們。 public void notifyObservers() { for (int i = observers.Count-1;i >=0 ;i -- ) { Observer observer = (Observer)observers[i]; observer.update(temperature ,humidity ,pressure ); } }
//當從氣象站得到更新觀測值時,我們通知觀察者。 public void measurementsChanged() { notifyObservers(); }
// 條件所限,所以我們用這個方法來手動更新觀測值。 public void setMeasurements(float temperature,float humidity,float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } #endregion }
public interface DisplayElement
{
void display();//當布告板顯示時,調用這個方法。
}
//這里我就寫了一個布告板,有興趣的朋友可以自己多寫幾個布告板,體驗下觀察者模式的精髓。
class CurrentConditionsDisplay:Observer ,DisplayElement { private float temperature; private float humidity; private Subject weatherData; public CurrentConditionsDisplay(Subject weatherData)//構造器需要 weatherData對象,作為注冊之用。 { this.weatherData = weatherData; weatherData.registerObserver(this ); } #region DisplayElement 成員 //把最近的溫度和濕度顯示出來 public void display() { Console.WriteLine("Current conditions:"+temperature + "F degrees and"+humidity +"% humidity"); } #endregion #region Observer 成員 //當update()被調用時,我們把溫度和濕度保存起來,然后調用display(). public void update(float temp, float humidity, float pressure) { this.temperature = temp; this.humidity = humidity; display(); } #endregion }
測試下:
static void Main(string[] args) { WeatherData weatherData = new WeatherData();//首先,建立一個WeatherData對象
//建立一個布告板並把WeatherData對象傳給它,currentDisplay 在構造器中往WeatherData對象注冊自己 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData );
//模擬新的氣象測量 weatherData.setMeasurements(80,65,30.4f); weatherData.setMeasurements(82,70,29.2f); weatherData.setMeasurements(78,90,29.2f); Console.ReadKey(); }
調試顯示: