天天看點

設計模式(9) ------------職責鍊模式 前言 動機 意圖 結構圖 代碼示例 現實場景 實作要點 運用效果 适用性 相關模式 總結

前言

之前我們已經将前兩種類型的設計模式都已經悉數介紹完呢(建立型和結構型),希望此時大家能夠對它們有一個全新的認識,不僅加深對各個模式的理論了解,更需要在日常開發活動中實踐之,這樣才能真正做到對模式的融會貫通。模式的學習和領悟需要一個過程,不可能一蹴而就,更不可高開低走,需要大家靜下心來紮實地一步步學習和提高。從今天開始,我們将繼續完成最後一種類型的設計模式——行為型模式的介紹和學習,所謂的行為型模式實際上涉及到算法和對象間職責的配置設定問題,行為型模式不僅是描述對象或者類的模式,還描述它們之間的通信模式。這些模式扁鵲了在運作時刻難以跟蹤的複雜控制流,将你的注意力從控制流轉移到對象間的聯系方式上來。同時行為型模式可分為行為類模式和行為對象模式兩種,前者是使用繼承機制在類間分派行為,後者是使用對象組合的方式來完成行為的分派。本文将對第一個行為型模式——職責鍊模式進行深入的介紹和學習,開始我們對行為型模式的探索之旅吧!

動機

在實際的軟體系統開發中,有時我們會面臨需要将操作請求轉發到一系列接收者對象組成的鍊式結構情況,之是以是一系列接收者而不是單個接收者是因為操作請求者并不知道清楚目前請求将交由具體某個接收者來完成,而每個接收者能夠處理的請求操作是有限度的,不可能面面俱到。面對一特定操作請求,它要麼能夠完全處理,完成對操作請求的相關任務,要麼就隻能向下傳遞給它認為可以勝任目前操作請求的下一個請求接收者對象,由其繼續完成目前操作請求任務。試想一下,如果我們事先不能建構這樣一條接收請求對象鍊,那麼在用戶端我們将不得不直接與處理特定請求的接收者打交道,這不僅增加了使用者程式對請求接收者的使用難度,而且也勢必造成兩者的緊耦合關系。上述場景便是職責鍊模式擅長之地,在職責鍊模式裡,若幹對象由每一個對象對其下家的引用而連接配接起來形成請求處理鍊,請求将從該鍊頭依次向鍊尾傳遞,直到遇到能夠完全處理目前請求的接收者對象,此時才會結束請求向下傳遞過程。而請求的發出者并不知道目前鍊中哪個對象能夠處理目前請求,這樣系統就可以在不影響用戶端的情況下動态地重新組織和配置設定職責,進而也就将具體操作請求與系列請求接收者解耦開來。接下來,讓我們深入地學習職責鍊模式,盡量做到了然于胸吧。

意圖

使多個對象都有機會處理請求,進而避免請求的發送者和接收者之間的耦合關系。将這些對象連成一條鍊,并沿着這條鍊傳遞該請求,直到有一個對象處理它為止。

結構圖

設計模式(9) ------------職責鍊模式 前言 動機 意圖 結構圖 代碼示例 現實場景 實作要點 運用效果 适用性 相關模式 總結

抽象處理者(Handler)角色:定義職責的接口,也就是在這裡定義處理請求的方法,亦可以在這裡實作後繼鍊。

具體處理者(ConcreteHandler)角色:實作職責的類,在這個類中,實作對在它職責範圍内請求的處理,如果處理不了,就繼續轉發請求給後繼者。

用戶端(client)角色:職責鍊的用戶端,向鍊上的具體處理驍送出請求,讓職責鍊負責處理。

代碼示例

public abstract class Handler{
       protected Handler successor;
       public void SetSuccessor(Handler handler){
           this.successor=successor;
       }
    
       public abstract void handleRequest(String request);
   }
    
  public class ConcreteHandler1 extends Handler{
      public void handleRequest(String request){
   
          //需要某些條件來判斷目前請求是否為自己所能處理的範圍,這裡隻是簡單說明下
          boolean someCondition=false;
          if(someCondition){
              System.out.println("ConcreteHandler1  handle request"+request);
          }else {
              if(successor!=null){
                  //既然目前處理器不能處理請求,那麼就直接将目前請求傳遞給它直接後繼處理器,由其繼續處理
                  successor.handleRequest(request);
              }
          }
      }
  }
   
  public class ConcreteHandler2 extends Handler{
      public void handleRequest(String request){
          boolean someCondition=false;
          if(someCondition){
              System.out.println("ConcreteHandler2  handle request"+request);
          }else {
              if(successor!=null){
                  successor.handleRequest(request);
              }else {
                  System.out.println("目前請求不能被所有處理者處理!");
              }
          }
      }
  }
   
  public class Client{
      public static void main(String[] args){
          ConcreteHandler1 handler1=new ConcreteHandler1();
          ConcreteHandler2 handler2=new ConcreteHandler2();
   
          handler1.SetSuccessor(handler2);
          handler2.SetSuccessor(null);
   
          handler1.handleRequest("just for test");            
      }
  }  
           

從示例代碼中,我們可以大緻清楚地明白職責鍊模式的實作方式,如果大家還記得之前我們介紹過的設計模式,應該對這段代碼會感覺似曾相識,還記得裝飾模式的示例程式嗎?首先,我們通過定義完成職責的統一接口,即Handler類,在其中,我們不僅定義了完成職責的接口,而且也儲存着其後繼處理器引用句柄,目的就是當目前處理器無法處理目前請求操作時,交給這個後繼處理器來處理,完成對請求傳遞過程,而不是停止于自己,讓有能力負責的處理器來完成對請求的處理工作。而具體的處理器ConcreteHandler1和ConcreteHandler2都是實作了自己處理請求的業務邏輯功能,也就是hanlerRequest方法。而在用戶端,我們根據業務需求将不同的具體處理器形成鍊式結構,然後直接将操作請求放置到處理鍊上進行處理即可,這樣一來,用戶端隻需要知道職責鍊的第一個處理對象即可,因為需要通過它将操作請求傳遞到職責鍊中。當然,用戶端也并不知道目前請求将由哪一個具體處理器接受處理,理想情況是不需要知道的。在這裡,需要提醒一點是,雖然職責鍊在示例代碼中是由用戶端直接建構生成的,但是也完全可以由不同的情景上下文對象根據實際的業務要求來生成不同處理能力的職責鍊,直接交由用戶端使用,而不需要用戶端負責對職責鍊的建立工作。

在實際的職責鍊模式實作裡,請求不一定會被處理,因為可能沒有合适的處理者,請求在職責鍊中從頭傳到尾,每個處理對象判斷不屬于自己處理,最後請求就沒有對象來處理,這一點需要明白。再者就是,在職責鍊模式裡有純與不不純之說,所謂“純”職責鍊模式,就是對于鍊上的每一個處理對象,要麼能有能力直接處理請求,結束請求在職責鍊上的傳遞,要麼就直接将請求原封不動地直接傳遞其後繼處理對象處理,而不能既對請求做部分操作又将部分處理過的請求傳遞給後繼處理對象。但是話雖如此,在現實的應用場景中,很難出現如此“純”的職責鍊模式,是以還是隻能看到“不純”的職責鍊實作。

現實場景

在現實的生活場景中,其實也不乏職責鍊模式原型的例子。比如企業裡的費用報帳活動就是一個比較鮮活的執行個體,一般來說,對于較小金額的費用報帳通過自己直系上司就可以獲得準許報帳,但是當報帳費用金額變大時,直系上司就無權做決定呢,需要将該報帳請求傳遞到其直系上司上呢,就這樣,随着報帳費用金額的不斷加大,報帳請求也就必将會依次傳遞到具有對目前報帳費用做決定的上司身上。當然,在這一鍊上的任一上司都有權利直接否決該報帳請求,也就是不将該報帳請求繼續往其直系上司傳遞呢,這也是真實場景可以發生的事情。但是不管最終哪一層級的上司有能力處理目前報帳請求,作為報帳人一開始隻能将該報帳請求傳遞給其直系上司,然後由他來決定是否需要将請求傳遞到更加層級的上司中,否則就是越級上報呢,現實中一般是不允許出現這樣的操作流程的:)。說到這裡,結合我們剛剛對職責鍊模式的介紹和示例代碼,大家應該能夠将上述場景出現的人、物與職責鍊模式中出現的角色一一對應了。首先,報帳請求者就是client,而報帳的費用就是request,而各個層級的上司就是各個具體的hanler對象,而企業中規定好各個層級上司所應具有的職權範疇,同時各個層級上司之間的上下級關系也是早已規定确定下來,也就是說自然形成了一個無形的職責鍊。下面我們就通過代碼來演繹下上述場景吧!

public abstract class Leader{
       protected Leader successor;
       public void SetSuccessor(Leader handler){
           this.successor=successor;
       }
    
       public abstract void handleRequest(long fee);
   }
    
  public class Manager extends Leader{
      public void handleRequest(long fee){
          if(fee<1000){
              //當報帳費用小于一千時,經理有權力來決定準許不準許,而無需上報上級上司
              //下面隻是簡單示意一下,現在情況下是可以批或者不批:)
              System.out.println("準許或者不準許目前報帳費用"+fee);
          }else {
              if(successor!=null){
                  //如果目前報帳費用金額已經超過經理能夠處理的範圍應該直接将該報帳請求傳遞到後繼上司進行處理,
                  //在這裡,也就是總經理呢。
                  successor.handleRequest(fee);
              }
          }
      }
  }
   
  public class President extends Leader{
      public void handleRequest(long fee){
          if(fee<10000){
              //當報帳費用小于一萬時,總經理有權力來決定準許不準許
              //下面隻是簡單示意一下,現在情況下是可以批或者不批:)
              System.out.println("準許或者不準許目前報帳費用"+fee);
          }else {
              System.out.println("超過公司報帳費用上限,直接否決:)");
              }
          }
      }
  }
   
  public class Client{
      public static void main(String[] args){
          Manager manager=new Manager();    
          President president=new President();
   
          manager.SetSuccessor(president);
          president.SetSuccessor(null);
   
          manager.handleRequest(500);
          manager.handleRequest(5000);        
      }
  }  
           

通過結構圖來表示如下:

設計模式(9) ------------職責鍊模式 前言 動機 意圖 結構圖 代碼示例 現實場景 實作要點 運用效果 适用性 相關模式 總結

由經理和總經理構成的鍊式結構簡單表示如下:

設計模式(9) ------------職責鍊模式 前言 動機 意圖 結構圖 代碼示例 現實場景 實作要點 運用效果 适用性 相關模式 總結

通過上圖能直覺地說明了經理是職責鍊的入口點,而總經理是職責鍊的最後處理者,如果它都無法處理,那該請求也隻能原樣傳回或者直接廢棄呢。但是對費用報帳者來說,他并不清楚目前報帳費用請求最終将被哪一層級上司處理,因為他本身并不需要知道哪一層級上司具有什麼職能,并且現實情況下,他也隻能将報帳請求交由其直系上司來處理,而不能越級上報:)

另外,熟悉java web開發的朋友對jsp 中的過濾器Filter肯定不會陌生,我們可以定義多個不同功能的過濾器,在web.xml檔案中配置和确定各個過濾器的執行過濾操作的先後順序,形成一個過濾鍊。這樣,從前台傳遞到背景的請求都必須依次通過目前過濾器鍊上的各個過濾器的過濾功能呢,最後請求才能進入到servlet中進行處理。從這點來說,過濾器鍊就是職責鍊模式的一種變形實作。

實作要點

  1. 實作後繼者鍊:有兩種方法可以實作後繼者鍊,一個是定義新的連結,也就是通過Handler來定義,示例代碼就是采用這種方式來完成;另一個是使用已有的連結,當已有的連結能夠支援所需要的功能時,完全可以複用它們,而不需要額外重新定義新的職責鍊。
  2. 連接配接後繼者:如有沒有已有的引用可定義一個鍊,那麼必須自己引入它們。這樣,在Handler裡,不僅要定義請求的接口,還需要維護後繼連結,通過ConcreteHandler預設情況下直接将請求轉發給後繼者。
  3. 表示請求:一個請求可以是原始類型,亦可以是複雜的對象,具體情況需要根據實際情況作決定。

運用效果

  1. 降低耦合度:職責鍊模式便利一個對象無需知道是由哪個處理器對象來處理其請求,對象隻需知道該請求會被“正解”處理。接收者和發送者都沒有對方明确的資訊,同時鍊中的對象無需知道鍊式結構。
  2. 增強了給對象指派職責的靈活性:可以通過在運作時刻對職責鍊進行動态的增加或者修改來增加或者改變處理一個請求的那些職責,也就是說可以對鍊中的任意處理對象進行修改,以滿足實際需要。
  3. 不保證被接受:既然一個請求沒有明确的接收者,也就不能保證請求一定會被處理,完全有可能請求到鍊的末端都得到不相應的處理對象的處理,當然我們可以對這種請求最終不能被處理情況定義一種預設的終結方式。
  4. 産生較多細粒度的對象:職責鍊會把功能處理分散到單獨的職責對象中,也就是一個職責對象隻會完成一種職責,在實作的業務處理過程中,需要很多的職責對象的組合,這樣也就不可避免地會産生較多細粒度的職責對象。

适用性

  1. 如果有多個對象可以處理同一個請求,但是具體由哪個對象來處理該請求,是運作時刻動态決定的的。這種情況使用職責鍊模式,把處理請求的對象實作為職責對象,再将它們構成一個職責鍊,當請求在這個鍊中傳遞的時候,具體由哪個職責對象來處理,在運作時刻進行判斷。
  2. 在不明确指定接收者的情況下,向多個對象中的其中一個送出請求。職責鍊模式将請求者與接收者之間解耦,請求者不需要知道究竟是哪一個接收者對象處理了請求。
  3. 需要動态地指定處理一個請求的對象集合時。職責鍊模式能動态地建構職責鍊,也就動态決定哪些職責對象參與到處理請求中來,這也就相當于動态地指定了處理一個請求的職責對象集合。

相關模式

  1. 職責鍊模式與組合模式:兩者可以組合使用。可以把各個職責對象通過組合模式來組合,這樣就可以通過組合對象自動遞歸地向上調用,由父元件作為子元件的後繼,進而形成鍊結構。
  2. 職責鍊模式與裝飾模式:兩者本質相似,都需要在運作期間動态組合,裝飾模式是動态組合裝飾器,而職責鍊是動态組合處理請求的職責對象鍊。同時兩者可以互相模拟實作對方的功能,裝飾模式能夠動态地給被裝飾對象添加功能,要求裝飾對象與被裝飾對象擁有相同的接口,而職責鍊模式可以實作動态的職責組合,标準的功能是有一個接收者處理了請求就結束,但是職責對象完成本職責後不急于結束,而是繼續往下傳遞請求的話,其功能與裝飾模式的功能就差不多呢。當然,從目的上來說,兩者還是具有很大不同的,裝飾模式是要實作透明的為對象添加功能,而職責鍊模式是要實作發送者和接收者解耦,另外裝飾模式可以無限遞歸調用,而職責鍊是有一個處理就結束。
  3. 職責鍊模式與政策模式:兩者可以組合使用。可以在職責鍊械的某個職責實作的時候,使用政策模式來選擇具體的實作,兩樣也可以在政策模式的某個政策袖中,使用職責鍊模式來實作功能處理,同理職責鍊械與可以與狀态模式組合使用,詳情說明将在後續文章中提及。

總結

職責模式的本質是:分離職責,動态組合。分離職責是前提,隻有先把複雜的功能分開,拆分成各個小功能,然後才能合理規劃和定義職責類;而動态組合才是職責鍊模式的精華所在,因為要實作請求對象和處理對象的解耦,請求對象并不知道最終的處理對象,是以需要動态地将可能的處理對象組合進來,也正因為組合是動态的,是以可以很友善地修改和增加親的處理對象,進而使系統具有更好的靈活性和擴充性。另外,由于職責對象隻完成一種職責,粒度較小,可以在多個不同功能的職責鍊中進行複用,增強職責功能的複用性。對職責鍊模式的介紹就至此為此吧,下一篇将繼續講述另一個行為型模式——指令模式,敬請期待!

參考資料:

  1. 程傑著《大話設計模式》一書
  2. 陳臣等著《研磨設計模式》一書
  3. GOF著《設計模式》一書
  4. Terrylee .Net設計模式系列文章
  5. 呂震宇老師 設計模式系列文章

轉載自:http://www.cnblogs.com/JackyTecblog/archive/2012/11/03/2752875.html

繼續閱讀