Java設計模式13:責任鏈模式

前言web

來菜鳥這個你們庭10個月了,總得來講比較融入了環境,同時在忙碌的工做中也深感技術積累不夠,在優秀的人身邊工做必須更加花時間去提高本身的技術能力、技術視野,因此開一個系列文章,標題就輕鬆一點叫作最近學習了XXX吧,記錄一下本身的學習心得。架構

因爲最近想對系統進行一個小改造,想到使用責任鏈模式會很是適合,所以就係統地學習總結了一下責任鏈模式,分享給你們。ide

 

責任鏈模式的定義與特色學習

責任鏈模式的定義:使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關係,將這個對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理他爲止優化

標準的責任鏈模式,我的總結下來有以下幾個特色:this

  • 鏈上的每一個對象都有機會處理請求
  • 鏈上的每一個對象都持有下一個要處理對象的引用
  • 鏈上的某個對象沒法處理當前請求,那麼它會把相同的請求傳給下一個對象

用一張圖表示如下使用了責任鏈模式以後的架構:spa

也就是說,責任鏈模式知足了請求發送者與請求處理者之間的鬆耦合,抽象非核心的部分,以鏈式調用的方式對請求對象進行處理設計

這麼說不明白?那麼下面經過實際例子讓你明白。3d

 

不使用責任鏈模式code

爲何要使用責任鏈模式,那麼咱們得知道不使用責任鏈模式有什麼壞處,而後經過使用責任鏈模式如何將代碼優化。

如今有一個場景:小明要去上學,媽媽給小明列了一些上學前須要作的清單(洗頭、吃早飯、洗臉),小明必須按照媽媽的要求,把清單上打鉤的事情作完了才能夠上學。

首先咱們定義一個準備列表PreparationList:

 1 public class PreparationList {
 2 
 3     /**
 4      * 是否洗臉
 5      */
 6     private boolean washFace;
 7     
 8     /**
 9      * 是否洗頭
10      */
11     private boolean washHair;
12     
13     /**
14      * 是否吃早餐
15      */
16     private boolean haveBreakfast;
17 
18     public boolean isWashFace() {
19         return washFace;
20     }
21 
22     public void setWashFace(boolean washFace) {
23         this.washFace = washFace;
24     }
25 
26     public boolean isWashHair() {
27         return washHair;
28     }
29 
30     public void setWashHair(boolean washHair) {
31         this.washHair = washHair;
32     }
33 
34     public boolean isHaveBreakfast() {
35         return haveBreakfast;
36     }
37 
38     public void setHaveBreakfast(boolean haveBreakfast) {
39         this.haveBreakfast = haveBreakfast;
40     }
41 
42     @Override
43     public String toString() {
44         return "ThingList [washFace=" + washFace + ", washHair=" + washHair + ", haveBreakfast=" + haveBreakfast + "]";
45     }
46     
47 }

定義了三件事情:洗頭、洗臉、吃早餐。

接着定義一個學習類,按媽媽要求,把媽媽要求的事情作完了再去上學:

 1 public class Study {
 2 
 3     public void study(PreparationList preparationList) {
 4         if (preparationList.isWashHair()) {
 5             System.out.println("洗臉");
 6         }
 7         if (preparationList.isWashHair()) {
 8             System.out.println("洗頭");
 9         }
10         if (preparationList.isHaveBreakfast()) {
11             System.out.println("吃早餐");
12         }
13         
14         System.out.println("我能夠去上學了!");
15     }
16     
17 }

這個例子實現了咱們的需求,可是不夠優雅,咱們的主流程是學習,可是把要準備作的事情這些動做耦合在學習中,這樣有兩個問題:

  • PreparationList中增長一件事情的時候,好比增長化妝、打掃房間,必須修改study方法進行適配
  • 當這些事情的順序須要發生變化的時候,必須修改study方法,好比先洗頭再洗臉,那麼7~9行的代碼必須和4~6行的代碼互換位置

最糟糕的寫法,只是爲了知足功能罷了,違背開閉原則,即當咱們擴展功能的時候須要去修改主流程,沒法作到對修改關閉、對擴展開放。

 

使用責任鏈模式

接着看一下使用責任鏈模式的寫法,既然責任鏈模式的特色是「鏈上的每一個對象都持有下一個對象的引用」,那麼咱們就這麼作。

先抽象出一個AbstractPrepareFilter:

 1 public abstract class AbstractPrepareFilter {
 2 
 3     private AbstractPrepareFilter nextPrepareFilter;
 4     
 5     public AbstractPrepareFilter(AbstractPrepareFilter nextPrepareFilter) {
 6         this.nextPrepareFilter = nextPrepareFilter;
 7     }
 8 
 9     public void doFilter(PreparationList preparationList, Study study) {
10         prepare(preparationList);
11         
12         if (nextPrepareFilter == null) {
13             study.study();
14         } else {
15             nextPrepareFilter.doFilter(preparationList, study);
16         }
17     }
18     
19     public abstract void prepare(PreparationList preparationList);
20     
21 }

留一個抽象方法prepare給子類去實現,在抽象類中持有下一個對象的引用nextPrepareFilter,若是有,則執行;若是沒有表示鏈上全部對象都執行完畢,執行Study類的study()方法:

 1 public class Study {
 2 
 3     public void study() {
 4         System.out.println("學習");
 5     }
 6     
 7 }

接着咱們實現AbstractPrepareList,就比較簡單了,首先是洗頭:

 1 public class WashFaceFilter extends AbstractPrepareFilter {
 2     
 3     public WashFaceFilter(AbstractPrepareFilter nextPrepareFilter) {
 4         super(nextPrepareFilter);
 5     }
 6 
 7     @Override
 8     public void prepare(PreparationList preparationList) {
 9         if (preparationList.isWashFace()) {
10             System.out.println("洗臉");
11         }
12         
13     }
14     
15 }

接着洗臉:

 1 public class WashHairFilter extends AbstractPrepareFilter {
 2     
 3     public WashHairFilter(AbstractPrepareFilter nextPrepareFilter) {
 4         super(nextPrepareFilter);
 5     }
 6 
 7     @Override
 8     public void prepare(PreparationList preparationList) {
 9         if (preparationList.isWashHair()) {
10             System.out.println("洗頭");
11         }
12         
13     }
14     
15 }

最後吃早餐:

 1 public class HaveBreakfastFilter extends AbstractPrepareFilter {
 2     
 3     public HaveBreakfastFilter(AbstractPrepareFilter nextPrepareFilter) {
 4         super(nextPrepareFilter);
 5     }
 6 
 7     @Override
 8     public void prepare(PreparationList preparationList) {
 9         if (preparationList.isHaveBreakfast()) {
10             System.out.println("吃早餐");
11         }
12         
13     }
14     
15 }

最後咱們看一下調用方如何編寫:

 1 @Test
 2 public void testResponsibility() {
 3     PreparationList preparationList = new PreparationList();
 4     preparationList.setWashFace(true);
 5     preparationList.setWashHair(false);
 6     preparationList.setHaveBreakfast(true);
 7         
 8     Study study = new Study();
 9         
10     AbstractPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter(null);
11     AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter);
12     AbstractPrepareFilter washHairFilter = new WashHairFilter(washFaceFilter);
13         
14     washHairFilter.doFilter(preparationList, study);
15 }

至此使用責任鏈模式修改這段邏輯完成,看到咱們完成了學習與準備工做之間的解耦,即核心的事情咱們是要學習,此時不管加多少準備工做,都不須要修改study方法,只須要修改調用方便可。

可是這種寫法好嗎?我的認爲這種寫法雖然符合開閉原則,可是兩個明顯的缺點對客戶端並不友好:

  • 增長、減小責任鏈對象,須要修改客戶端代碼,即好比我這邊想要增長一個打掃屋子的操做,那麼testResponsibility()方法須要改動
  • AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter)這種調用方式不夠優雅,客戶端須要思考一下,到底真正調用的時候調用三個Filter中的哪一個Filter

爲此,咱們來個終極版的、升級版的責任鏈模式。

 

升級版責任鏈模式

上面咱們寫了一個責任鏈模式,這種是一種初級的符合責任鏈模式的寫法,最後也寫了,這種寫法是有明顯的缺點的,那麼接着咱們看一下升級版的責任鏈模式如何寫,解決上述問題。

如下的寫法也是Servlet的實現方式,首先仍是抽象一個Filter:

 1 public interface StudyPrepareFilter {
 2 
 3     public void doFilter(PreparationList preparationList, FilterChain filterChain);
 4     
 5 }

注意這裏多了一個FilterChain,也就是責任鏈,是用於串起全部的責任對象的,它也是StudyPrepareFilter的一個子類:

 1 public class FilterChain implements StudyPrepareFilter {
 2 
 3     private int pos = 0;
 4 
 5     private Study study;
 6     
 7     private List<StudyPrepareFilter> studyPrepareFilterList;
 8     
 9     public FilterChain(Study study) {
10         this.study = study;
11     }
12 
13     public void addFilter(StudyPrepareFilter studyPrepareFilter) {
14         if (studyPrepareFilterList == null) {
15             studyPrepareFilterList = new ArrayList<StudyPrepareFilter>();
16         }
17         
18         studyPrepareFilterList.add(studyPrepareFilter);
19     }
20     
21     @Override
22     public void doFilter(PreparationList thingList, FilterChain filterChain) {
23         // 全部過濾器執行完畢
24         if (pos == studyPrepareFilterList.size()) {
25             study.study();
26         }
27         
28         studyPrepareFilterList.get(pos++).doFilter(thingList, filterChain);
29     }
30     
31 }

即這裏有一個計數器,假設全部的StudyPrepareFilter沒有調用完畢,那麼調用下一個,不然執行Study的study()方法。

接着就比較簡單了,實現StudyPrepareFilter類便可,首先仍是洗頭:

 1 public class WashHairFilter implements StudyPrepareFilter {
 2 
 3     @Override
 4     public void doFilter(PreparationList preparationList, FilterChain filterChain) {
 5         if (preparationList.isWashHair()) {
 6             System.out.println("洗完頭髮");
 7         }
 8         
 9         filterChain.doFilter(preparationList, filterChain);
10     }
11     
12 }

注意,這裏每一個實現類須要顯式地調用filterChain的doFilter方法。洗臉:

 1 public class WashFaceFilter implements StudyPrepareFilter {
 2 
 3     @Override
 4     public void doFilter(PreparationList preparationList, FilterChain filterChain) {
 5         if (preparationList.isWashFace()) {
 6             System.out.println("洗完臉");
 7         }
 8         
 9         filterChain.doFilter(preparationList, filterChain);
10     }
11     
12 }

吃早飯:

 1 public class HaveBreakfastFilter implements StudyPrepareFilter {
 2 
 3     @Override
 4     public void doFilter(PreparationList preparationList, FilterChain filterChain) {
 5         if (preparationList.isHaveBreakfast()) {
 6             System.out.println("吃完早飯");
 7         }
 8         
 9         filterChain.doFilter(preparationList, filterChain);
10     }
11     
12 }

最後看一下調用方:

 1 @Test
 2 public void testResponsibilityAdvance() {
 3     PreparationList preparationList = new PreparationList();
 4     preparationList.setWashFace(true);
 5     preparationList.setWashHair(false);
 6     preparationList.setHaveBreakfast(true);
 7         
 8     Study study = new Study();
 9         
10     StudyPrepareFilter washFaceFilter = new WashFaceFilter();
11     StudyPrepareFilter washHairFilter = new WashHairFilter();
12     StudyPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter();
13         
14     FilterChain filterChain = new FilterChain(study);
15     filterChain.addFilter(washFaceFilter);
16     filterChain.addFilter(washHairFilter);
17     filterChain.addFilter(haveBreakfastFilter);
18         
19     filterChain.doFilter(preparationList, filterChain);
20 }

完美解決初版責任鏈模式存在的問題,至此增長、修改責任對象客戶端調用代碼都不須要再改動。

有的人可能會問,你這個增長、減小責任對象,testResponsibilityAdvance()方法,不是還得addFilter,或者刪除一行嗎?咱們回想一下,Servlet咱們增長或減小Filter須要改動什麼代碼嗎?不用,咱們須要改動的只是web.xml而已。一樣的道理,FilterChain裏面有studyPrepareFilterList,咱們徹底能夠把FilterChain作成一個Spring Bean,全部的Filter具體實現類也都是Spring Bean,注入studyPrepareFilterList就行了,僞代碼爲:

1 <bean id="filterChain" class="xxx.xxx.xxx.FilterChain">
2     <property name="studyPrepareFilterList">
3         <list>
4             <ref bean="washFaceFilter" />
5             <ref bean="washHairFilter" />
6             <ref bean="haveBreakfastFilter" />
7         </list>
8     </property>
9 </bean>

這樣是否是完美解決了問題?咱們新增、減小Filter,或者修改Filter順序,只須要修改.xml文件便可,不只核心邏輯符合開閉原則,調用方也符合開閉原則。

 

責任鏈模式的使用場景

這個就很少說了,最典型的就是Servlet中的Filter,有了上面的分析,你們應該也能夠理解Servlet中責任鏈模式的工做原理了,而後爲何一個一個的Filter須要配置在web.xml中。

 

責任鏈模式的結構

想一想看,好像責任鏈模式也沒有什麼太複雜的結構,將責任抽象,實現責任接口,客戶端發起調用,網上找了一張圖表示一下:

 

 

責任鏈模式的優勢及使用場景

最後說說責任鏈模式的優勢吧,大體有如下幾點:

  • 實現了請求發送者與請求處理者之間的鬆耦合
  • 可動態添加責任對象、刪除責任對象、改變責任對象順序,很是靈活
  • 每一個責任對象專一於作本身的事情,職責明確

何時須要用責任鏈模式?這個問題我是這麼想的:系統設計的時候,注意區分主次就好,即哪部分是核心流程,哪部分是輔助流程,輔助流程是否有N多if...if...if...的場景,若是是且每一個if都有一個統一的抽象,那麼抽象輔助流程,把每一個if做爲一個責任對象進行鏈式調用,優雅實現,易複用可擴展。

相關文章
相關標籤/搜索