今天給大家介紹責任鏈模式(Chain of Responsibility Pattern),責任鏈模式是屬于設(shè)計模式中的行為型模式。巧用設(shè)計模式可以讓我們的代碼解耦,提高可拓展性和維護性,但一定不要為了使用而使用。
一、定義
- 原文:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.
使多個對象都有機會處理請求,從而避免了請求的發(fā)送者和接受者之間的耦合關(guān)系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止。
- 維基百科定義:In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain.
在面向?qū)ο蟮脑O(shè)計中,責任鏈模式是一種由命令對象的源和一系列處理對象組成的設(shè)計模式。每個處理對象都包含定義其可以處理的命令對象類型的邏輯。其余的將傳遞到鏈中的下一個處理對象。
- 簡要總結(jié):責任鏈模式是將鏈中的每一個節(jié)點看做一個對象,且內(nèi)部維護下一個節(jié)點對象的引用,形成一個單鏈表,當收到一個請求時,會沿著鏈的路徑一次傳遞給每一個節(jié)點對象,當然每個節(jié)點對象可以選擇繼續(xù)傳遞或者提前退出,直到最后一個節(jié)點對象。
二、應(yīng)用
1. 應(yīng)用場景
責任鏈模式的使用也是比較廣泛的,比如我們OA系統(tǒng)的審批流,或者某一個特殊的業(yè)務(wù)需要經(jīng)過幾個比較復雜的流程處理等,比如一個添加業(yè)務(wù),假設(shè)我們先要進行數(shù)據(jù)校驗,然后進行入庫操作,最后要通知其他系統(tǒng)做特殊處理,就可以做成一個責任鏈模式,代碼整體耦合性降低,易于維護。
2. 角色
-
Handler(抽象處理者)
定義請求處理方法,以及維護下一個處理者的引用
-
ConcreteHandler(具體處理者)
處理請求的具體角色
3. 類圖
責任鏈模式的類圖比較簡單,所有處理節(jié)點都繼承同一個父類,這個父類可以是抽象類或者具體實現(xiàn)類,類中維護下一個處理節(jié)點的引用以及節(jié)點的處理請求方法(這個方法可以是抽象方法,也可以是具體實現(xiàn),當然還可以利用鉤子方法實現(xiàn)對子類方法的調(diào)用),調(diào)用者不關(guān)注具體節(jié)點的實現(xiàn),只調(diào)用節(jié)點的處理方法即可。

4. 舉例說明
假設(shè)現(xiàn)在有一個請假流程,小于三天組長審批即可,大于3天同時需要CTO審批,大于10天同時需要CEO審批。組長和CTO以及CEO都有權(quán)利審批通過或者駁回,如果審批通過,根據(jù)請假天數(shù)判斷是否需要流轉(zhuǎn)到下一節(jié)點,如果駁回請求,則直接返回。
首先看下整體類圖:

首先創(chuàng)建一個父類處理器,
public class RequestHandler {
private RequestHandler next; // 下一個節(jié)點的引用
public RequestHandler(RequestHandler next) {
this.next = next;
}
public void handleRequest(Request request) {
if (next != null) { // 如果下一個節(jié)點不為空,則繼續(xù)調(diào)用下一個節(jié)點的處理方法
next.handleRequest(request);
}
}
}
創(chuàng)建三個節(jié)點處理對象,分別是組長處理、CTO處理、CEO處理:
public class LeaderHandler extends RequestHandler {
public LeaderHandler(RequestHandler next) {
super(next);
}
@Override
public void handleRequest(Request request) {
if (request.getEmployeeName().equals("張三")) {
System.out.println("組長駁回【 " + request.getEmployeeName() + " 】請假");
request.setReject();
return;
}
System.out.println("組長通過【 " + request.getEmployeeName() + " 】請假");
if (request.getDays() > 3) {
super.handleRequest(request);
} else {
request.setPass();
}
}
}
public class CTOHandler extends RequestHandler {
public CTOHandler(RequestHandler next) {
super(next);
}
@Override
public void handleRequest(Request request) {
if (request.getEmployeeName().equals("李四")) {
System.out.println("CTO駁回【 " + request.getEmployeeName() + " 】請假");
request.setReject();
return;
}
System.out.println("CTO通過【 " + request.getEmployeeName() + " 】請假");
if (request.getDays() > 10) {
super.handleRequest(request);
} else {
request.setPass();
}
}
}
public class CEOHandler extends RequestHandler {
public CEOHandler(RequestHandler next) {
super(next);
}
@Override
public void handleRequest(Request request) {
if (request.getEmployeeName().equals("王五")) {
System.out.println("CEO駁回【 " + request.getEmployeeName() + " 】請假");
request.setReject();
return;
}
System.out.println("CEO通過【 " + request.getEmployeeName() + " 】請假");
request.setPass();
}
}
然后創(chuàng)建一個請假處理對象:
public class Request {
private String employeeName; // 員工姓名
private int days; // 請假天數(shù)
private boolean handleResult; // 請假是否通過
public Request(String employeeName, int days) {
this.employeeName = employeeName;
this.days = days;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public int getDays() {
return days;
}
public void setDays(int days) {
this.days = days;
}
public void setPass() {
this.handleResult = true;
}
public void setReject() {
this.handleResult = false;
}
public boolean getHandleResult() {
return this.handleResult;
}
}
建立一個測試類:
public class ChainTest {
public static void main(String[] args) {
RequestHandler chain = new LeaderHandler(new CTOHandler(new CEOHandler(null)));
Request zhangsan = new Request("張三", 3);
chain.handleRequest(zhangsan);
System.out.println("張三請假結(jié)果:" + zhangsan.getHandleResult());
System.out.println("-------------------");
Request lisi = new Request("李四", 5);
chain.handleRequest(lisi);
System.out.println("李四請假結(jié)果:" + lisi.getHandleResult());
System.out.println("-------------------");
Request wangwu = new Request("王五", 15);
chain.handleRequest(wangwu);
System.out.println("王五請假結(jié)果:" + wangwu.getHandleResult());
System.out.println("-------------------");
Request oldZhao = new Request("老趙", 15);
chain.handleRequest(oldZhao);
System.out.println("老趙請假結(jié)果:" + oldZhao.getHandleResult());
}
}
打印結(jié)果如下:
組長駁回【 張三 】請假
張三請假結(jié)果:false
-------------------
組長通過【 李四 】請假
CTO駁回【 李四 】請假
李四請假結(jié)果:false
-------------------
組長通過【 王五 】請假
CTO通過【 王五 】請假
CEO駁回【 王五 】請假
王五請假結(jié)果:false
-------------------
組長通過【 老趙 】請假
CTO通過【 老趙 】請假
CEO通過【 老趙 】請假
老趙請假結(jié)果:true
我們可以看到輸入不同的參數(shù),所走的業(yè)務(wù)流程不同,有的只經(jīng)過一個處理器,有的會走完所有的處理器。
三、和建造者模式的結(jié)合使用
上面的示例中,在父類維護next節(jié)點的引用我們是通過構(gòu)造器來維護的,所以我們在初始化處理鏈的時候是通過下面代碼實現(xiàn)的
RequestHandler chain = new LeaderHandler(new CTOHandler(new CEOHandler(null)));
可以看到代碼整體顯得比較臃腫,如果我們有六七個或者更多處理節(jié)點的時候,易讀性會非常差,
而大多數(shù)情況下一般都是通過父類提供一個setNextHandler(RequestHandler handler)來維護下一個節(jié)點,如果用此方法來初始化處理鏈的話,代碼如下:
LeaderHandler leader = new LeaderHandler();
CTOHandler cto = new CTOHandler();
CEOHandler ceo = new CEOHandler();
leader.setNextHandler(cto);
cto.setNextHandler(ceo);
我們可以看到需要不斷的給當前節(jié)點設(shè)置下一個節(jié)點,易讀性和維護性也是比較差的,至此我們想到了利用建造者模式和責任鏈模式的結(jié)合使用,來讓此處的設(shè)計變的解耦又好維護。
我們在RequestHandler類中維護一個內(nèi)部類,內(nèi)部類中使用單向鏈表來維護處理節(jié)點的順序,再通過addHandler方法添加所有處理鏈,在調(diào)用的時候提供一個build方法獲取頭結(jié)點,然后通過頭節(jié)點的next屬性獲取后續(xù)所有的節(jié)點,這樣只要我們在初始化處理鏈的時候按順序添加處理結(jié)點,就不用在設(shè)置next結(jié)點,代碼的可讀性也提升了很多。
public class RequestHandler {
private RequestHandler next;
public void setNextHandler(RequestHandler next) {
this.next = next;
}
public void handleRequest(Request request) {
if (next != null) {
next.handleRequest(request);
}
}
public static class Builder {
private RequestHandler head;
private RequestHandler tail;
public Builder addHandler(RequestHandler requestHandler) {
if (head == null) {
this.head = this.tail = requestHandler;
} else {
this.tail.next = requestHandler;
this.tail = requestHandler;
}
return this;
}
public RequestHandler build() {
return this.head;
}
}
}
調(diào)用的時候通過如下代碼即可:
public class ChainTest {
public static void main(String[] args) {
RequestHandler.Builder builder = new RequestHandler.Builder();
builder.addHandler(new LeaderHandler())
.addHandler(new CTOHandler())
.addHandler(new CEOHandler());
RequestHandler handler = builder.build();
Request zhangsan = new Request("張三", 3);
handler.handleRequest(zhangsan);
System.out.println("張三請假結(jié)果:" + zhangsan.getHandleResult());
System.out.println("-------------------");
Request lisi = new Request("李四", 5);
handler.handleRequest(lisi);
System.out.println("李四請假結(jié)果:" + lisi.getHandleResult());
System.out.println("-------------------");
Request wangwu = new Request("王五", 15);
handler.handleRequest(wangwu);
System.out.println("王五請假結(jié)果:" + wangwu.getHandleResult());
System.out.println("-------------------");
Request oldZhao = new Request("老趙", 15);
handler.handleRequest(oldZhao);
System.out.println("老趙請假結(jié)果:" + oldZhao.getHandleResult());
}
}
四、在源碼中的體現(xiàn)
public void log(LogRecord record) {
if (!isLoggable(record.getLevel())) {
return;
}
Filter theFilter = config.filter;
if (theFilter != null && !theFilter.isLoggable(record)) {
return;
}
// Post the LogRecord to all our Handlers, and then to
// our parents' handlers, all the way up the tree.
Logger logger = this;
while (logger != null) {
final Handler[] loggerHandlers = isSystemLogger
? logger.accessCheckedHandlers()
: logger.getHandlers();
for (Handler handler : loggerHandlers) {
handler.publish(record);
}
final boolean useParentHdls = isSystemLogger
? logger.config.useParentHandlers
: logger.getUseParentHandlers();
if (!useParentHdls) {
break;
}
logger = isSystemLogger ? logger.parent : logger.getParent();
}
}
此處是把所有的handler都維護在一個數(shù)組中,然后通過循環(huán)handler調(diào)用next的處理結(jié)點
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
Assert.state(this.request == null, "This FilterChain has already been called!");
if (this.iterator == null) {
this.iterator = this.filters.iterator();
}
if (this.iterator.hasNext()) {
Filter nextFilter = (Filter)this.iterator.next();
nextFilter.doFilter(request, response, this);
}
this.request = request;
this.response = response;
}
這里是將所有的Filter放到list中,然后通過調(diào)用doFilter()方法時循環(huán)迭代list,list中的Filter也會順序執(zhí)行。
五、優(yōu)缺點
優(yōu)點:
- 降低代碼的耦合度,將請求與處理解耦
- 節(jié)點對象只需關(guān)注自己要處理的業(yè)務(wù)或者特定的請求即可,對于不想處理的可以直接傳遞給下一個節(jié)點
- 鏈路結(jié)構(gòu)靈活,可以通過改變鏈路結(jié)構(gòu)動態(tài)的新增或者刪減責任
- 可以實時新增鏈路節(jié)點,符合開閉原則
缺點:
- 責任鏈太長或者處理時間過長,會影響整體性能
- 整體調(diào)用有點類似遞歸,排查問題較為費力
- 代碼拆分的小類會比較多