前言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 }
這個例子實現了咱們的需求,可是不夠優雅,咱們的主流程是學習,可是把要準備作的事情這些動做耦合在學習中,這樣有兩個問題:
最糟糕的寫法,只是爲了知足功能罷了,違背開閉原則,即當咱們擴展功能的時候須要去修改主流程,沒法作到對修改關閉、對擴展開放。
使用責任鏈模式
接着看一下使用責任鏈模式的寫法,既然責任鏈模式的特色是「鏈上的每一個對象都持有下一個對象的引用」,那麼咱們就這麼作。
先抽象出一個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方法,只須要修改調用方便可。
可是這種寫法好嗎?我的認爲這種寫法雖然符合開閉原則,可是兩個明顯的缺點對客戶端並不友好:
爲此,咱們來個終極版的、升級版的責任鏈模式。
升級版責任鏈模式
上面咱們寫了一個責任鏈模式,這種是一種初級的符合責任鏈模式的寫法,最後也寫了,這種寫法是有明顯的缺點的,那麼接着咱們看一下升級版的責任鏈模式如何寫,解決上述問題。
如下的寫法也是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做爲一個責任對象進行鏈式調用,優雅實現,易複用可擴展。