[Head First設計模式]山西面館中的設計模式——裝飾者模式html
不知不自覺又將設計模式融入生活了,吃個飯也不得安生,也發現生活中的不少場景,均可以用設計模式來模擬。原來設計模式就在我身邊。設計模式
爲何觀察者模式會出現呢?安全
爲了創建一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應作出反應。在此,發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標能夠對應多個觀察者,並且這些觀察者之間沒有相互聯繫,能夠根據須要增長和刪除觀察者,使得系統更易於擴展,這就是爲何須要觀察者模式。ide
觀察者模式(Observer Pattern):定義了對象之間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆獲得通知並被自動更新。觀察者模式又被稱爲發佈-訂閱(Publish/Subscribe)模式,模型-視圖(Model/View)模式,源-監聽器(Source/Listener)模式,或從屬者(Dependents)模式。觀察者模式是一種對象行爲型的模式。post
當兩個對象是鬆耦合的,它們之間可以交互,可是相互瞭解的不多。測試
觀察者模式提供了主題和觀察者之間的鬆耦合設計。由於主題只知道觀察者實現了某個接口(即Observer接口)。主題不須要知道具體觀察者是誰,作了些什麼或其它任何細節。要增長新的觀察者或刪除觀察者,主題不會受到任何影響,沒必要修改主題代碼。this
能夠獨立地複用主題和觀察者,他們之間互不影響,便是鬆耦合。url
設計原則:在交互的對象之間爭取鬆耦合設計spa
因爲鬆耦合設計使得對象間的依賴最小化,因此,咱們可以建立柔性的oo系統,應對變化的狀況,由於對象間的依賴降到了最低。設計
書中的氣象站例子:
代碼實現:
1 public interface Subject 2 { 3 void RegisterObserver(Observer o);//這兩個方法都須要一個觀察者做爲參數,該觀察者是用來註冊或被刪除的。 4 void RemoveObserver(Observer o); 5 void NotifyObservers();//當主題改變狀態時,這個方法會被調用,以通知全部的觀察者 6 }
1 public interface Observer 2 { 3 /// <summary> 4 /// 當氣象觀測值改變時,主題會把這些狀態值做爲方法的參數傳給觀察者 5 /// </summary> 6 /// <param name="temp"></param> 7 /// <param name="humidity"></param> 8 /// <param name="pressure"></param> 9 void Update(float temp,float humidity,float pressure); 10 }
1 /// <summary> 2 /// 該接口之包含一個方法,也就是display方法,當佈告板須要顯示時,調用次方法。 3 /// </summary> 4 public interface DisplayElement 5 { 6 void Display(); 7 }
在WeatherData中實現主題接口
1 /// <summary> 2 /// WeatherData實現了subject接口 3 /// </summary> 4 public class WeatherData : Subject 5 { 6 /// <summary> 7 /// 咱們加上一個ArrayList來記錄觀察者,此ArrayList是在構造器中創建的 8 /// </summary> 9 private ArrayList observers; 10 private float temperature; 11 private float humidity; 12 private float pressure; 13 public WeatherData() 14 { 15 observers = new ArrayList(); 16 } 17 public void RegisterObserver(Observer o) 18 { 19 //當註冊觀察者的時候,秩序把他們加在ArrayList後面就好了 20 observers.Add(o); 21 } 22 23 public void RemoveObserver(Observer o) 24 { 25 //一樣,當觀察者想取消註冊,只須要移除 26 int i = observers.IndexOf(o); 27 if (i > 0) 28 { 29 observers.Remove(i); 30 } 31 } 32 /// <summary> 33 /// 有趣的地方來了,在這裏,咱們把狀態告訴每個觀察者, 34 /// 由於觀察者都實現了Update方法,因此咱們知道如何通知他們 35 /// </summary> 36 public void NotifyObservers() 37 { 38 for (int i = 0; i < observers.Count; i++) 39 { 40 Observer observer = (Observer)observers[i]; 41 observer.Update(temperature, humidity, pressure); 42 } 43 } 44 /// <summary> 45 /// 當氣象站獲得更新觀測值的時,咱們通知觀察者。 46 /// </summary> 47 public void MeasurementChanged() { 48 NotifyObservers(); 49 } 50 public void SetMeasurements(float temperature, float humidity, float pressure) 51 { 52 this.temperature = temperature; 53 this.humidity = humidity; 54 this.pressure = pressure; 55 } 56 }
創建佈告板
1 /// <summary> 2 /// 此佈告板實現了Observer接口,因此能夠從weatherdata對象中得到改變, 3 /// 同時也實現了DisplayElement接口,由於咱們的API規定全部的佈告板必須實現此接口 4 /// </summary> 5 public class CurrentConditionsDisplay:Observer,DisplayElement 6 { 7 private float temperature; 8 private float humidity; 9 10 private Subject weatherData; 11 /// <summary> 12 /// 構造器須要weatherdata對象做爲註冊用 13 /// </summary> 14 /// <param name="weatherData"></param> 15 public CurrentConditionsDisplay(Subject weatherData) 16 { 17 this.weatherData = weatherData; 18 weatherData.RegisterObserver(this); 19 } 20 public void Update(float temp, float humidity, float pressure) 21 { 22 //當update被調用的時候,咱們把溫度和溼度保存起來,而後調用Display(); 23 this.temperature = temp; 24 this.humidity = humidity; 25 Display(); 26 27 28 } 29 /// <summary> 30 /// 只是將最近的溼度和溫度顯示出來。 31 /// </summary> 32 public void Display() 33 { 34 Console.WriteLine("Current conditions:"+temperature+" F degrees and"+humidity+"% humidity"); 35 } 36 }
測試代碼
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 //首先建立weatherData對象 6 WeatherData weatherData = new WeatherData(); 7 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); 8 weatherData.SetMeasurements(80, 65, 30.4f); 9 //通知 10 weatherData.NotifyObservers(); 11 weatherData.SetMeasurements(82, 70, 29.2f); 12 weatherData.NotifyObservers(); 13 weatherData.SetMeasurements(78, 90, 29.2f); 14 weatherData.NotifyObservers(); 15 Console.Read(); 16 } 17 }
結果:
場景:山西面館中,我點餐,服務員傳話給廚師,廚師應答,廚師作飯。
分析:在這個場景中,我:被觀察者,服務員,廚師:觀察者。
我:「一份西紅柿雞蛋湯麪」
一系列動做
服務員:1.向廚師傳話「一份西紅柿雞蛋湯麪」
廚師:1.收到,2.開始作。
在.NET中,C#使用委託以及事件,能夠很好的實現觀察者模式。委託至關於「訂閱清單」的角色,當目標中關聯了該委託的事件被觸發時,則委託將自動按序執行觀察者註冊於委託中的方法。
代碼實現:
基類的實現
1 /// <summary> 2 /// 自定義事件參數 3 /// </summary> 4 public class EatSomthingEventArgs : EventArgs 5 { 6 private string foodName; 7 public EatSomthingEventArgs(string foodName) 8 { 9 this.foodName = foodName; 10 } 11 public string FoodName 12 { 13 get { return foodName; } 14 set { foodName = value; } 15 }
1 /// <summary> 2 /// 聲明一個委託,用於代理一系列自定義方法 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 public delegate void EatSomethingEventHandler(object sender, EatSomthingEventArgs e);
1 /// <summary> 2 /// 在Observer Pattern(觀察者模式)中,此類做爲全部Subject(主題)的抽象基類 3 /// 此抽象類無抽象方法,主要是爲了避免能實例化該類對象,確保模式完整性. 4 /// 具體的主題(好比:customer)就繼承自該類,也能夠說是被觀察者的基類。 5 /// </summary> 6 public abstract class Subject 7 { 8 public event EatSomethingEventHandler EatSomethingHandler; 9 public string FoodName { set; get; } 10 /// <summary> 11 /// 封裝了觸發事件的方法 12 /// 主要爲了規範化及安全性,除觀察者基類外,其派生類不直接觸發委託事件 13 /// </summary> 14 protected void Notify() 15 { 16 if (EatSomethingHandler != null) 17 { 18 EatSomethingHandler(this, new EatSomthingEventArgs(this.FoodName)); 19 } 20 } 21 }
1 /// <summary> 2 /// 此類做爲全部Observer(觀察者)的抽象基類 3 /// 此類做爲觀察者基類,用於規劃全部觀察者(即訂閱方)訂閱行爲 4 /// 具體實施過程: 5 /// 1.指定觀察者所觀察的對象(即發佈方).(經過構造器傳遞) 6 /// 2.規劃觀察者自身須要做出響應方法列表 7 /// 3.註冊須要委託執行的方法.(經過構造器實現) 8 /// </summary> 9 public abstract class Observer 10 { 11 /// <summary> 12 /// 構造時經過傳入具體主題subject,把觀察者與模型關聯,並完成訂閱. 13 /// </summary> 14 /// <param name="subject"></param> 15 public Observer(Subject subject) 16 { 17 subject.EatSomethingHandler += new EatSomethingEventHandler(Response); 18 } 19 /// <summary> 20 /// 規劃了觀察者的一種行爲(方法),全部派生於該觀察者基類的具體觀察者都 21 /// 經過覆蓋該方法來實現做出響應的行爲. 22 /// </summary> 23 /// <param name="sender"></param> 24 /// <param name="e"></param> 25 public abstract void Response(object sender, EatSomthingEventArgs e); 26 }
1 /// <summary> 2 /// 另外一個觀察者基類.該觀察者類型擁有兩個響應行爲 3 /// </summary> 4 public abstract class Observer2 5 {/// <summary> 6 /// 構造時經過傳入具體主題subject,把觀察者與模型關聯,並完成訂閱. 7 /// </summary> 8 /// <param name="subject"></param> 9 public Observer2(Subject subject) 10 { 11 subject.EatSomethingHandler += new EatSomethingEventHandler(Response); 12 subject.EatSomethingHandler += new EatSomethingEventHandler(Response2); 13 } 14 /// <summary> 15 /// 規劃了觀察者的一種行爲(方法),全部派生於該觀察者基類的具體觀察者都 16 /// 經過覆蓋該方法來實現做出響應的行爲. 17 /// </summary> 18 /// <param name="sender"></param> 19 /// <param name="e"></param> 20 public abstract void Response(object sender, EatSomthingEventArgs e); 21 public abstract void Response2(object sender, EatSomthingEventArgs e); 22 }
觀察者實現
1 /// <summary> 2 /// 廚師類 繼承自抽象觀察者基類Observer2 兩個反應 3 /// </summary> 4 public class Cook : Observer2 5 { 6 public Cook(Subject subject) 7 : base(subject) 8 { } 9 public override void Response(object sender, EatSomthingEventArgs e) 10 { 11 Console.WriteLine("廚師:收到了,要作一份{0}", e.FoodName); 12 } 13 14 public override void Response2(object sender, EatSomthingEventArgs e) 15 { 16 Console.WriteLine("廚師:{0}已經在作了,稍等.....", e.FoodName); 17 } 18 }
1 /// <summary> 2 /// 具體的觀察者 服務員 繼承自觀察者基類 3 /// </summary> 4 public class Waiter : Observer 5 { 6 public Waiter(Subject subject) 7 : base(subject) 8 { } 9 public override void Response(object sender, EatSomthingEventArgs e) 10 { 11 Console.WriteLine("美女服務員:康師傅作一份{0}", e.FoodName); 12 } 13 }
被觀察者
1 /// <summary> 2 /// 具體的主題類 即被觀察者 3 /// </summary> 4 public class Customer : Subject 5 { 6 public Customer(string foodName) 7 { 8 base.FoodName = foodName; 9 } 10 /// <summary> 11 /// 訂餐方法 此方法將觸發觀察者的一系列動做 12 /// </summary> 13 public void OrderMeal() 14 { 15 Console.WriteLine("顧客:我要一份{0}", base.FoodName); 16 base.Notify(); 17 } 18 }
測試:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Customer customer = new Customer("西紅柿雞蛋湯麪"); 6 Waiter waiter = new Waiter(customer); 7 Cook cook = new Cook(customer); 8 //訂餐 動做 9 customer.OrderMeal(); 10 Console.Read(); 11 } 12 }
結果:
當一個對象的數據更新時須要通知其餘對象,但這個對象又不但願和被通知的那些對象造成緊耦合。
當一個對象的數據更新時,這個對象須要讓其餘對象也各自更新本身的數據,但這個對象不知道具體有多少對象須要更新數據
觀察者模式與備忘錄模式的關係
觀察者模式使用了備忘錄模式(Memento Pattern),暫時將觀察者對象存儲在被觀察者對象裏面
觀察者模式與MVC模式的關係
觀察者模式能夠用來實現MVC模式。觀察者模式中的主題即是MVC模式中的模型加控制器,而觀察者即是視圖
通常狀況下,MVC是觀察者模式、組合模式、策略模式等設計模式的組合。
觀察者模式的有點
1,具體主題和具體觀察者是鬆耦合關係。
因爲主題(Subject)接口僅僅依賴於觀察者(Observer)接口,所以具體主題只是知道它的觀察者是實現觀察者(Observer)接口的某個類的實例,但不須要知道具體是哪一個類。一樣,因爲觀察者僅僅依賴於主題(Subject)接口,所以具體觀察者只是知道它依賴的主題是實現主題(subject)接口的某個類的實例,但不須要知道具體是哪一個類。
2,觀察者模式知足「開-閉原則」。
主題(Subject)接口僅僅依賴於觀察者(Observer)接口,這樣,咱們就可讓建立具體主題的類也僅僅是依賴於觀察者(Observer)接口,所以若是增長新的實現觀察者(Observer)接口的類,沒必要修改建立具體主題的類的代碼。一樣,建立具體觀察者的類僅僅依賴於主題(Observer)接口,若是增長新的實現主題(Subject)接口的類,也沒必要修改建立具體觀察者類的代碼。
觀察者模式的缺點
參考書
Head First 設計模式