深入淺出設計模式(18)——Observer模式


一、Observer模式的意圖:

在對象的內部狀態發生變化時,自動通知外部對象進行響應。

二、Observer模式的構成:

·被觀察者:內部狀態有可能被改變,而且又需要通知外部的對象

·觀察者:需要對內部狀態的改變做出響應的對象



三、Observer模式的Java實現:

Java的API中已經為我們提供了Observer模式的實現。具體由java.util.Observable類和java.util.Observer接口完成。

前者有兩個重要的方法:

·setChanged:設置內部狀態為已改變

·notifyObservers(Object obj):通知觀察者所發生的改變,參數obj是一些改變的信息

后者有一個核心方法:

·update(Object obj):相應被觀察者的改變,其中obj就是被觀察者傳遞過來的信息,該方法會在notifyObservers被調用時自動調用。

下面是Observer模式的實現過程:

·創建一個被觀察者,繼承java.util.Observable

·創建一個觀察者,實現java.util.Observer接口

·注冊觀察着,調用addObserver(Observer observer)

·在被觀察者改變對象內部狀態的地方,調用setChanged()方法,然后調用notifyObservers(Object)方法,通知被觀察者

·在觀察者的update(Object)方法中,對改變做出響應。

四、Observer模式的好處:

1.Observer模式的優點:

   ·被觀察者只需要知道誰在觀察它,無需知道具體的觀察細節

   ·被觀察者一旦發生變化,只需要通過廣播的方式告知觀察者,至於消息如何到達則不需知道。這樣的話無疑消除了被觀察者和觀察者之間通信的硬編碼

   ·當一個被觀察者同時被多個觀察着觀察時,觀察者可以只選擇自己感興趣的事件,而忽略其它的事件
  
   ·多個觀察者組合起來可以形成一個觀察鏈,如果一旦需要回滾多個操作,此時觀察鏈可以發揮作用

   ·觀察者可以實時對被觀察對象的變化做出響應,例如自動告警、中斷運行等


2.運用Observer模式可以

   ·屏蔽線程間的通信機制:例如兩個線程之間,主線程可以作為觀察者,執行線程是被觀察者。彼此之間只知道對方存在,但不知道之間通信的細節

   ·消除硬編碼:如果沒有Observer模式,則只能采用回調的模式,或者在代碼中顯示地調用觀察者

   ·優化異常機制:特別適合在異常發生時向頂層監控,減少try-catch代碼量

 

五、實例

    假設我們有個高檔的熱水器,我們給它通上電,當水溫超過95度的時候:1、揚聲器會開始發出語音,告訴你水的溫度;2、液晶屏也會改變水溫的顯示,來提示水已經快燒開了。

    現在我們需要寫個程序來模擬這個燒水的過程,我們將定義一個類來代表熱水器,我們管它叫:Heater,它有代表水溫的字段,叫做temperature;當然,還有必不可少的給水加熱方法BoilWater(),一個發出語音警報的方法MakeAlert(),一個顯示水溫的方法,ShowMsg()。

 
 
  1. namespace Delegate {  
  2.     class Heater {  
  3.     private int temperature; // 水溫  
  4.     // 燒水  
  5.     public void BoilWater() {  
  6.         for (int i = 0; i < = 100; i++) {  
  7.            temperature = i;  
  8.  
  9.            if (temperature > 95) {  
  10.                MakeAlert(temperature);  
  11.                ShowMsg(temperature);  
  12.             }  
  13.         }  
  14.     }  
  15.  
  16.     // 發出語音警報  
  17.     private void MakeAlert(int param) {  
  18.        Console.WriteLine("Alarm:嘀嘀嘀,水已經 {0} 度了:" , param);  
  19.     }  
  20.       
  21.     // 顯示水溫  
  22.     private void ShowMsg(int param) {  
  23.        Console.WriteLine("Display:水快開了,當前溫度:{0}度。" , param);  
  24.     }  
  25. }  
  26.  
  27. class Program {  
  28.     static void Main() {  
  29.        Heater ht = new Heater();  
  30.        ht.BoilWater();  
  31.     }  
  32. }  

Observer設計模式簡介

    上面的例子顯然能完成我們之前描述的工作,但是卻並不夠好。現在假設熱水器由三部分組成:熱水器、警報器、顯示器,它們來自於不同廠商並進行了組裝。那么,應該是熱水器僅僅負責燒水,它不能發出警報也不能顯示水溫;在水燒開時由警報器發出警報、顯示器顯示提示和水溫。

    這時候,上面的例子就應該變成這個樣子:  

 
 
  1. // 熱水器  
  2. public class Heater {   
  3.     private int temperature;  
  4.           
  5.     // 燒水  
  6.     private void BoilWater() {  
  7.        for (int i = 0; i < = 100; i++) {  
  8.            temperature = i;  
  9.         }  
  10.     }  
  11. }  
  12.  
  13. // 警報器  
  14. public class Alarm{  
  15.     private void MakeAlert(int param) {  
  16.        Console.WriteLine("Alarm:嘀嘀嘀,水已經 {0} 度了:" , param);  
  17.     }  
  18. }  
  19.  
  20. // 顯示器  
  21. public class Display{  
  22.     private void ShowMsg(int param) {  
  23.        Console.WriteLine("Display:水已燒開,當前溫度:{0}度。" , param);  
  24.     }  

    這里就出現了一個問題:如何在水燒開的時候通知報警器和顯示器?在繼續進行之前,我們先了解一下Observer設計模式,Observer設計模式中主要包括如下兩類對象:

    Subject:監視對象,它往往包含着其他對象所感興趣的內容。在本范例中,熱水器就是一個監視對象,它包含的其他對象所感興趣的內容,就是temprature字段,當這個字段的值快到100時,會不斷把數據發給監視它的對象。

    Observer:監視者,它監視Subject,當Subject中的某件事發生的時候,會告知Observer,而Observer則會采取相應的行動。在本范例中,Observer有警報器和顯示器,它們采取的行動分別是發出警報和顯示水溫。

    在本例中,事情發生的順序應該是這樣的:

    警報器和顯示器告訴熱水器,它對它的溫度比較感興趣(注冊)。

    熱水器知道后保留對警報器和顯示器的引用。

    熱水器進行燒水這一動作,當水溫超過95度時,通過對警報器和顯示器的引用,自動調用警報器的MakeAlert()方法、顯示器的ShowMsg()方法。

    類似這樣的例子是很多的,GOF對它進行了抽象,稱為Observer設計模式:Observer設計模式是為了定義對象間的一種一對多的依賴關系,以便於當一個對象的狀態改變時,其他依賴於它的對象會被自動告知並更新。Observer模式是一種松耦合的設計模式。

實現范例的Observer設計模式

    我們之前已經對委托和事件介紹很多了,現在寫代碼應該很容易了,現在在這里直接給出代碼,並在注釋中加以說明。

 
 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4.  
  5. namespace Delegate {  
  6.     // 熱水器  
  7.     public class Heater {  
  8.        private int temperature;  
  9.        public delegate void BoilHandler(int param);   //聲明委托  
  10.        public event BoilHandler BoilEvent;        //聲明事件  
  11.  
  12.        // 燒水  
  13.        public void BoilWater() {  
  14.            for (int i = 0; i < = 100; i++) {  
  15.               temperature = i;  
  16.  
  17.               if (temperature > 95) {  
  18.                   if (BoilEvent != null) { //如果有對象注冊  
  19.                       BoilEvent(temperature);  //調用所有注冊對象的方法  
  20.                   }  
  21.               }  
  22.            }  
  23.        }  
  24.     }  
  25.  
  26.     // 警報器  
  27.     public class Alarm {  
  28.        public void MakeAlert(int param) {  
  29.            Console.WriteLine("Alarm:嘀嘀嘀,水已經 {0} 度了:", param);  
  30.        }  
  31.     }  
  32.  
  33.     // 顯示器  
  34.     public class Display {  
  35.        public static void ShowMsg(int param) { //靜態方法  
  36.            Console.WriteLine("Display:水快燒開了,當前溫度:{0}度。", param);  
  37.        }  
  38.     }  
  39.       
  40.     class Program {  
  41.        static void Main() {  
  42.            Heater heater = new Heater();  
  43.            Alarm alarm = new Alarm();  
  44.  
  45.            heater.BoilEvent += alarm.MakeAlert;    //注冊方法  
  46.            heater.BoilEvent += (new Alarm()).MakeAlert;   //給匿名對象注冊方法  
  47.            heater.BoilEvent += Display.ShowMsg;       //注冊靜態方法  
  48.  
  49.            heater.BoilWater();   //燒水,會自動調用注冊過對象的方法  
  50.        }  
  51.     }  

    輸出為:

 
 
  1. Alarm:嘀嘀嘀,水已經 96 度了:  
  2. Alarm:嘀嘀嘀,水已經 96 度了:  
  3. Display:水快燒開了,當前溫度:96度。  
  4. // 省略... 

 



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com