[Head First設計模式]山西面館中的設計模式——觀察者模式

系列文章

[Head First設計模式]山西面館中的設計模式——裝飾者模式html

引言

不知不自覺又將設計模式融入生活了,吃個飯也不得安生,也發現生活中的不少場景,均可以用設計模式來模擬。原來設計模式就在我身邊。設計模式

爲何觀察者模式會出現呢?安全

爲了創建一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應作出反應。在此,發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標能夠對應多個觀察者,並且這些觀察者之間沒有相互聯繫,能夠根據須要增長和刪除觀察者,使得系統更易於擴展,這就是爲何須要觀察者模式。ide

觀察者模式定義

觀察者模式(Observer Pattern):定義了對象之間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆獲得通知並被自動更新。觀察者模式又被稱爲發佈-訂閱(Publish/Subscribe)模式,模型-視圖(Model/View)模式,源-監聽器(Source/Listener)模式,或從屬者(Dependents)模式。觀察者模式是一種對象行爲型的模式。post

觀察者模式UML

 

當兩個對象是鬆耦合的,它們之間可以交互,可是相互瞭解的不多。測試

觀察者模式提供了主題和觀察者之間的鬆耦合設計。由於主題只知道觀察者實現了某個接口(即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)接口的類,也沒必要修改建立具體觀察者類的代碼。

觀察者模式的缺點

  1. 若是一個被觀察者對象有不少直接和間接的觀察者的話,將全部的觀察者都通知到會花費不少時間。
  2. 若是在被觀察者之間有循環依賴的話,給觀察者會觸發它們之間進行循環調用,致使系統崩潰。在使用觀察者模式時要特別注意這一點。
  3. 雖然觀察者模式能夠隨時使觀察者知道所觀察的對象發生了變化,可是觀察者模式沒有相應的機制是觀察者知道所觀察的對象是怎麼發生變化的

 參考書

Head First 設計模式

相關文章
相關標籤/搜索