1.初識職責鏈模式
使多個對象都有機會處理請求,從而避免請求的發(fā)送者和接收者之間的耦合關系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它為止。

- Handler:定義職責的接口,通常在這里定義處理請求的方法,可以在這里實現(xiàn)后繼鏈。
ConcreteHandler:實現(xiàn)職責的類,在這個類里面,實現(xiàn)對在它職責范圍內請求的處理,如果不處理,就繼續(xù)轉發(fā)請求給后繼者。
Client:職責鏈的客戶端,向鏈上的具體處理者對象提交請求,讓職責鏈負責處理。
2.體會職責鏈模式
2.1 場景問題——申請聚餐費
用來考慮這樣一個功能:申請聚餐費用的管理。
申請聚餐費用的大致流程一般是:由申請人先填寫申請單,然后交給領導審查,如果申請批準下來了,領導會通知申請人審批通過,然后申請人去財務核領費用,如果沒有核準,領導會通知申請人審批未通過,此事也就此作罷了。
不同級別的領導,對于審批的額度是不一樣的,比如:項目經(jīng)理只能審批 500元以內的申請;部門經(jīng)理能審批1000元以內的申請;而總經(jīng)理可以審核任意額度的申請。
也就是說,當某人提出聚餐費用申請的請求后,該請求會由項目經(jīng)理、部門經(jīng)理、總經(jīng)理之中的某一位領導來進行相應的處理,但是提出申請的人并不知道最終會由誰來處理他的請求,一般申請人是把自己的申請?zhí)峤唤o項目經(jīng)理,或許最后是由總經(jīng)理來處理他的請求,但是申請人并不知道應該由總經(jīng)理來處理他申請請求。
那么該怎樣實現(xiàn)這樣的功能呢?
2.2 不用模式的解決方案
有何問題:
上面的實現(xiàn)很簡單,基本上沒難度。仔細想想,這么實現(xiàn)有沒有問題呢?
仔細分析申請聚餐費用的業(yè)務功能和目前的實現(xiàn),主要面臨著如下問題:
- 1)聚餐費用申請的處理流程是可能會變動的。
- 2)各個處理環(huán)節(jié)的業(yè)務處理也是會變動的。
如果采用上面的實現(xiàn),要是處理的邏輯發(fā)生了變化,解決的方法,一個是生成一個子類,覆蓋requestToProjectManager方法,然后在里面實現(xiàn)新的處理;另一個方法就是修改處理申請方法的源碼。
總之都不是什么好方法,也就是說,如果出現(xiàn)聚餐費用申請的處理流程變化的情況,或者是出現(xiàn)各個處理環(huán)節(jié)的功能變化的時候,上面的實現(xiàn)方式是很難靈活的變化來適應新功能的要求的。
把上面的問題抽象一下:客戶端發(fā)出一個請求,會有很多對象都可以來處理這個請求,而且不同對象的處理邏輯是不一樣的。對于客戶端而言,無所謂誰來處理,反正有對象處理就可以了。
而且在上述處理中,還希望處理流程是可以靈活變動的,而處理請求的對象需要能方便的修改或者是被替換掉,以適應新的業(yè)務功能的需要。
請問如何才能實現(xiàn)上述要求?
2.3 使用模式的解決方案
2.3.1 使用模式來解決的思路
仔細分析上面的場景,當客戶端提出一個聚餐費用的申請,后續(xù)處理這個申請的對象,項目經(jīng)理、部門經(jīng)理和總經(jīng)理,自然的形成了一個鏈,從項目經(jīng)理.部門經(jīng)理.總經(jīng)理,客戶端的申請請求就在這個鏈中傳遞,直到有領導處理為止。看起來,上面的功能要求很適合采用職責鏈來處理這個業(yè)務。
要讓處理請求的流程可靈活的變動,一個基本的思路,那就是動態(tài)構建流程步驟,這樣隨時都可以重新組合出新的流程來。而要讓處理請求的對象也要很靈活,那就要讓它足夠簡單,最好是只實現(xiàn)單一的功能,或者是有限的功能,這樣更有利于修改和復用。
職責鏈模式就很好的體現(xiàn)了上述的基本思路,首先職責鏈模式會定義一個所有處理請求的對象都要繼承實現(xiàn)的抽象類,這樣就有利于隨時切換新的實現(xiàn);其次每個處理請求對象只實現(xiàn)業(yè)務流程中的一步業(yè)務處理,這樣使其變得簡單;最后職責鏈模式會動態(tài)的來組合這些處理請求的對象,把它們按照流程動態(tài)組合起來,并要求它們依次調用,這樣就動態(tài)的實現(xiàn)了流程。
這樣一來,如果流程發(fā)生了變化,只要重新組合就好了;如果某個處理的業(yè)務功能發(fā)生了變化,一個方案是修改該處理對應的處理對象,另一個方案是直接提供一個新的實現(xiàn),然后在組合流程的時候,用新的實現(xiàn)替換掉舊的實現(xiàn)就可以了。
2.3.2 使用模式來解決的類圖

3.理解職責鏈模式
3.1 認識職責鏈模式
3.1.1 職責鏈模式的功能
職責鏈模式主要用來處理: “客戶端發(fā)出一個請求,有多個對象都有機會來處理這一個請求,但是客戶端不知道究竟誰會來處理他的請求 ”,這樣的情況。也就是需要讓請求者和接收者解耦,這樣就可以動態(tài)的切換和組合接收者了。
要注意在標準的職責鏈模式里面,是只要有對象處理了請求,這個請求就到此為止,不再被傳遞和處理了。
如果是要變形使用職責鏈,就可以讓這個請求繼續(xù)傳遞,每個職責對象對這個請求進行一定的功能處理,從而形成一個處理請求的功能鏈。
3.1.2 隱式接收者
當客戶端發(fā)出請求的時候,客戶端并不知道誰會真正處理他的請求,客戶端只知道他提交請求的第一個對象。從第一個處理對象開始,整個職責鏈里面的對象,要么自己處理請求,要么繼續(xù)轉發(fā)給下一個接收者。
也就是對于請求者而言,并不知道最終的接收者是誰,但是一般情況下,總是會有一個對象來處理的,因此稱為隱式接收者。
3.1.3 如何構建鏈
職責鏈的鏈怎么構建呢?這是個大問題,實現(xiàn)的方式也是五花八門,歸結起來大致有以下一些方式。
首先是按照實現(xiàn)的地方來說:
- 1)可以實現(xiàn)在客戶端,在提交請求前組合鏈,也就是在使用的時候動態(tài)組合鏈,稱為外部鏈;
- 2)可以在Handler里面實現(xiàn)鏈的組合,算是內部鏈的一種;
- 3)可以在各個職責對象里面,由各個職責對象自行決定后續(xù)的處理對象,這種實現(xiàn)方式要求每個職責對象除了進行業(yè)務處理外,還必須了解整個業(yè)務流程。
按照構建鏈的數(shù)據(jù)來源,也就是決定了按照什么順序來組合鏈的數(shù)據(jù),又分為幾種:
- 1)一種就是在程序里面動態(tài)組合;
- 2)可以通過外部,如數(shù)據(jù)庫來獲取組合的數(shù)據(jù),這種屬于數(shù)據(jù)庫驅動的方式;
- 3)還有一種方式就是通過配置文件傳遞進來,也可以是流程的配置文件。
如果是從外部獲取數(shù)據(jù)來構建鏈,那么在程序運行的時候,會讀取這些數(shù)據(jù),然后根據(jù)數(shù)據(jù)的要求來獲取相應的對象,并組合起來。
還有一種是不需要構建鏈,因為已有的對象已經(jīng)自然構成鏈了,這種情況多出現(xiàn)在組合模式構建的對象樹中,這樣子對象可以很自然的向上找到自己的父對象。就像部門人員的組織結構一樣,頂層是總經(jīng)理,總經(jīng)理下面是各個部門的經(jīng)理,部門經(jīng)理下面是項目經(jīng)理,項目經(jīng)理下面是各個普通員工,自然就可以形成:普通員工->項目經(jīng)理->部門經(jīng)理->總經(jīng)理這樣的鏈。
3.1.4 誰來處理
職責鏈中那么多處理對象,到底誰來處理請求呢,這個是在運行時期動態(tài)決定的。當請求被傳遞到某個處理對象的時候,這個對象會按照已經(jīng)設定好的條件來判斷,是否屬于自己處理的范圍,如果是就處理,如果不是就轉發(fā)請求給下一個對象。
3.1.5 請求一定會被處理嗎?
在職責鏈模式中,請求不一定會被處理,因為可能沒有合適的處理者,請求在職責鏈里面從頭傳遞到尾,每個處理對象都判斷不屬于自己處理,最后請求就沒有對象來處理。這一點是需要注意的。
可以在職責鏈的末端始終加上一個不支持此功能處理的職責對象,這樣如果傳遞到這里,就會出現(xiàn)提示,本職責鏈沒有對象處理這個請求。
3.2 處理多種請求
前面的示例都是同一個職責鏈處理一種請求的情況,現(xiàn)在有這樣的需求,還是費用申請的功能,這次是申請預支差旅費,假設還是同一流程,也就是組合同一個職責鏈,從項目經(jīng)理.傳遞給部門經(jīng)理.傳遞給總經(jīng)理,雖然流程相同,但是每個處理類需要處理兩種請求,它們的具體業(yè)務邏輯是不一樣的,那么該如何實現(xiàn)呢?
1.簡單的處理方式
要解決這個問題,也不是很困難,一個簡單的方法就是為每種業(yè)務單獨定義一個方法,然后客戶端根據(jù)不同的需要調用不同的方法,還是通過代碼來示例一下。注意這里故意的把兩個方法做的有些不一樣,一個是返回String類型的值,一個是返回boolean類型的值;另外一個是返回到客戶端再輸出信息,一個是直接在職責處理里面就輸出信息。
2.通用請求的處理方式
上面的實現(xiàn)看起來很容易,但是仔細想想,這樣實現(xiàn)有沒有什么問題呢?
有一個很明顯的問題,那就是只要增加一個業(yè)務,就需要修改職責的接口,這是很不靈活的,Java開發(fā)中很強調面向接口編程,因此接口應該相對保持穩(wěn)定,接口一改,需要修改的地方就太多了,頻繁修改接口絕對不是個好主意。
那有沒有什么好方法來實現(xiàn)呢?分析一下現(xiàn)在變化的東西:
- 1)一是不同的業(yè)務需要傳遞的業(yè)務數(shù)據(jù)不同;
- 2)二是不同的業(yè)務請求的方法不同;
- 3)三是不同的職責對象處理這些不同的業(yè)務請求的業(yè)務邏輯不同
現(xiàn)在有一種簡單的方式,可以較好的解決這些問題。首先定義一套通用的調用框架,用一個通用的請求對象來封裝請求傳遞的參數(shù);然后定義一個通用的調用方法,這個方法不去區(qū)分具體業(yè)務,所有的業(yè)務都是這一個方法,那么具體的業(yè)務如何區(qū)分呢,就是在通用的請求對象里面會有一個業(yè)務的標記;到了職責對象里面,愿意處理就跟原來一樣的處理方式,如果不愿意處理,就傳遞到下一個處理對象就好了。
接下來看看如何在不改動現(xiàn)有的框架的前提下,擴展新的業(yè)務,這樣才能說明這種設計的靈活性。
3.3 功能鏈
在實際開發(fā)中,經(jīng)常會出現(xiàn)一個把職責鏈稍稍變形的用法。在標準的職責鏈中,一個請求在職責鏈中傳遞,只要有一個對象處理了這個請求,就會停止。
現(xiàn)在稍稍變一下,改成一個請求在職責鏈中傳遞,每個職責對象負責處理請求的某一方面功能,處理完成后,不是停止,而是繼續(xù)向下傳遞請求,當請求通過很多職責對象處理過后,功能也就處理完了,把這樣的職責鏈稱為功能鏈。
考慮這樣一個功能,在實際應用開發(fā)中,在進行業(yè)務處理之前,通常需要進行權限檢查、通用數(shù)據(jù)校驗、數(shù)據(jù)邏輯校驗等處理,然后才開始真正的業(yè)務邏輯實現(xiàn)??梢园堰@些功能分散到一個功能鏈中,這樣做的目的是使程序結構更加靈活,而且復用性會更好,比如通用的權限檢查就只需要做一份,然后就可以在多個功能鏈中使用了。
有些朋友看到這里,可能會想,這不是可以使用裝飾模式來實現(xiàn)嗎?沒錯,可以使用裝飾模式來實現(xiàn)這樣的功能,但是職責鏈會更靈活一些,因為裝飾模式是在已有的功能上增加新的功能,多個裝飾器之間會有一定的聯(lián)系;而職責鏈模式的各個職責對象實現(xiàn)的功能,相互之間是沒有關聯(lián)的,是自己實現(xiàn)屬于自己處理的那一份功能。
可能有些朋友會想到這很類似于在Web應用開發(fā)中的過濾器Filter,沒錯,過濾器鏈就類似于一個功能鏈,每個過濾器負責自己的處理,然后轉交給下一個過濾器,直到把所有的過濾器都走完,然后進入到Servlet里面進行處理。最常見的過濾器功能,比如權限檢查、字符集轉換等,基本上都是Web應用的標配。
接下來在示例中,實現(xiàn)這樣的功能:實現(xiàn)商品銷售的業(yè)務處理,在真正進行銷售的業(yè)務處理之前,需要對傳入處理的數(shù)據(jù),進行權限檢查、通用數(shù)據(jù)檢查和數(shù)據(jù)邏輯檢查,只有這些檢查都能通過的情況下,才說明傳入的數(shù)據(jù)是正確的、有效的數(shù)據(jù),才可以進行真正的業(yè)務功能處理。
3.4 職責鏈模式的優(yōu)缺點
- 請求者和接收者松散耦合
- 動態(tài)組合職責
- 產(chǎn)生很多細粒度對象
- 不一定能被處理
4.思考職責鏈模式
4.1 職責鏈模式的本質
職責鏈模式的本質是:分離職責,動態(tài)組合
4.2 何時選用
- 1)如果有多個對象可以處理同一個請求,但是具體由哪個對象來處理該請求,是運行時刻動態(tài)確定的。這種情況可以使用職責鏈模式,把處理請求的對象實現(xiàn)成為職責對象,然后把它們構成一個職責鏈,當請求在這個鏈中傳遞的時候,具體由哪個職責對象來處理,會在運行時動態(tài)判斷
- 2)如果你想在不明確指定接收者的情況下,向多個對象中的一個提交一個請求的話,可以使用職責鏈模式,職責鏈模式實現(xiàn)了請求者和接收者之間的解耦,請求者不需要知道究竟是哪一個接收者對象來處理了請求。
- 3)如果想要動態(tài)指定處理一個請求的對象集合,可以使用職責鏈模式,職責鏈模式能動態(tài)的構建職責鏈,也就是動態(tài)的來決定到底哪些職責對象來參與到處理請求中來,相當于是動態(tài)指定了處理一個請求的職責對象集合
5.示例——論壇帖子的過濾
實例代碼參考github
論壇用戶發(fā)表帖子,但是常常會有用戶一些不良的信息,如廣告信息,涉黃信息,涉及政治的敏感詞等。
定義所有責任鏈對象的父類:
/**
* 帖子處理器
*/
public abstract class PostHandler {
/**
* 后繼者
*/
protected PostHandler successor;
public void setSuccessor(PostHandler handler){
this.successor = handler;
}
public abstract void handlerRequest(Post post);
protected final void next(Post post){
if(this.successor != null){
this.successor.handlerRequest(post);
}
}
}
父類 Handler 主要封裝了傳遞請求等方法,其中要注意的有:
- successor,后繼者,這個屬性很重要,它保存了責任鏈中下一個處理器
- 在 next() 方法中(方法名自己隨便?。斦埱髠鬟f到最后一個責任對象時,已經(jīng)沒有后繼者繼續(xù)處理請求了,因此要對 successor 做判空處理,避免拋出空指針異常。
- 處理請求的handlerRequest 的入?yún)⒑头祷仡愋涂梢愿鶕?jù)實際情況修改,可以在該方法中拋出異常來中斷請求
廣告處理器:
/**
* 廣告處理器
*/
public class AdHandler extends PostHandler {
@Override
public void handlerRequest(Post post) {
//屏蔽廣告內容
String content = post.getContent();
//.....
content = content.replace("廣告","**");
post.setContent(content);
System.out.println("過濾廣告...");
//傳遞給下一個處理器
next(post);
}
}
涉黃處理器:
/**
* 涉黃處理器
*/
public class YellowHandler extends PostHandler {
@Override
public void handlerRequest(Post post) {
//屏蔽涉黃內容
String content = post.getContent();
//.....
content = content.replace("涉黃","**");
post.setContent(content);
System.out.println("過濾涉黃內容...");
//傳遞給下一個處理器
next(post);
}
}
敏感處理器:
/**
* 敏感詞處理器
*/
public class SensitiveWordsHandler extends PostHandler {
@Override
public void handlerRequest(Post post) {
//屏蔽敏感詞
String content = post.getContent();
//.....
content = content.replace("敏感詞","**");
post.setContent(content);
System.out.println("過濾敏感詞...");
//傳遞給下一個處理器
next(post);
}
}
測試:
//創(chuàng)建責任對象
PostHandler adHandler = new AdHandler();
PostHandler yellowHandler = new YellowHandler();
PostHandler swHandler = new SensitiveWordsHandler();
//形成責任鏈
yellowHandler.setSuccessor(swHandler);
adHandler.setSuccessor(yellowHandler);
Post post = new Post();
post.setContent("我是正常內容,我是廣告,我是涉黃,我是敏感詞,我是正常內容");
System.out.println("過濾前的內容為:"+post.getContent());
adHandler.handlerRequest(post);
System.out.println("過濾后的內容為:"+post.getContent());
5.1 為什么用責任鏈模式?
直接將過濾不良信息寫在一個方法里不行嗎?比如:
public class PostUtil {
public void filterContent(Post post){
String content = post.getContent();
content = content.replace("廣告","**");
content = content.replace("涉黃","**");
content = content.replace("敏感詞","**");
post.setContent(content);
}
}
如果后面要增加其他的功能,過濾其他類型的內容,我們還得修改上面的 filterContent 方法,違背了開閉原則。
因此我們需要使用責任鏈模式,能夠在不修改已有代碼的情況下擴展新功能。
6.案例
6.1 Servlet 中的 Filter
Servlet 中的過濾器 Filter 就是典型的責任鏈模式,假如我們要給每一次Http 請求都打印一個 log,就可以使用 Filter過濾器來實現(xiàn):
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("write log");
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
}
}
然后將這個過濾器配置到 web.xml 中:
<filter>
<filter-name>LogFilter</filter-name>
<filter-class>com.zhoujun.filter.LogFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LogFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在上面LogFilter類中,我們可以看到servlet的責任鏈是通過 Filter 來實現(xiàn)的,這是一個接口,在doFilter中還用到了 FilterChain ,也是一個接口。通過查找源碼,發(fā)現(xiàn)了 FilterChain 的其中一個實現(xiàn)類:
public class PassThroughFilterChain implements FilterChain {
@Nullable
private Filter filter;
@Nullable
private FilterChain nextFilterChain;
@Nullable
private Servlet servlet;
public PassThroughFilterChain(Filter filter, FilterChain nextFilterChain) {
Assert.notNull(filter, "Filter must not be null");
Assert.notNull(nextFilterChain, "'FilterChain must not be null");
this.filter = filter;
this.nextFilterChain = nextFilterChain;
}
public PassThroughFilterChain(Servlet servlet) {
Assert.notNull(servlet, "Servlet must not be null");
this.servlet = servlet;
}
public void doFilter(ServletRequest request, ServletResponse response) throws ServletException, IOException {
if (this.filter != null) {
this.filter.doFilter(request, response, this.nextFilterChain);
} else {
Assert.state(this.servlet != null, "Neither a Filter not a Servlet set");
this.servlet.service(request, response);
}
}
}
該類中的 Private FilterChain nextFilterChain; 相當于 PostHandler 中的后繼者 Successor。
將我們自定義的 Filter 配置到 web.xml 中的操作就是將該對象添加到責任鏈上,Servlet 開發(fā)者幫我們完成了 setSuccessor() 的操作。