責任鏈模式
責任鏈,顧名思義,就是用來處理相關事務責任的一條執行鏈,執行鏈上有多個節點,每個節點都有機會(條件匹配)處理請求事務,如果某個節點處理完了就可以根據實際業務需求傳遞給下一個節點繼續處理或者返回處理完畢。
這種模式給予請求的類型,對請求的發送者和接收者進行解耦。屬於行為型模式。
在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那么它會把相同的請求傳給下一個接收者,依此類推。
先來看一段代碼
public void test(int i, Request request){
if(i==1){
Handler1.response(request);
}else if(i == 2){
Handler2.response(request);
}else if(i == 3){
Handler3.response(request);
}else if(i == 4){
Handler4.response(request);
}else{
Handler5.response(request);
}
}
代碼的業務邏輯是這樣的,方法有兩個參數:整數 i 和一個請求 request,根據 i 的值來決定由誰來處理 request,如果 i1,由 Handler1來處理,如果 i2,由 Handler2 來處理,以此類推。在編程中,這種處理業務的方法非常常見,所有處理請求的類由if…else…條件判斷語句連成一條責任鏈來對請求進行處理,相信大家都經常用到。這種方法的優點是非常直觀,簡單明了,並且比較容易維護,但是這種方法也存在着幾個比較令人頭疼的問題:
- 代碼臃腫:實際應用中的判定條件通常不是這么簡單地判斷是否為1或者是否為2,也許需要復雜的計算,也許需要查詢數據庫等等,這就會有很多額外的代碼,如果判斷條件再比較多,那么這個if…else…語句基本上就沒法看了。
- 耦合度高:如果我們想繼續添加處理請求的類,那么就要繼續添加if…else…判定條件;另外,這個條件判定的順序也是寫死的,如果想改變順序,那么也只能修改這個條件語句。
既然缺點我們已經清楚了,就要想辦法來解決。這個場景的業務邏輯很簡單:如果滿足條件1,則由 Handler1 來處理,不滿足則向下傳遞;如果滿足條件2,則由 Handler2 來處理,不滿足則繼續向下傳遞,以此類推,直到條件結束。其實改進的方法也很簡單,就是把判定條件的部分放到處理類中,這就是責任連模式的原理。
定義
責任鏈模式(Chain of Responsibility Pattern):使多個對象都有機會處理請求,從而避免了請求的發送者和接受者之間的耦合關系。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它為止。
角色
- Handler: 抽象處理類,抽象處理類中主要包含一個指向下一處理類的成員變量nextHandler和一個處理請求的方法handRequest,handRequest方法的主要主要思想是,如果滿足處理的條件,則有本處理類來進行處理,否則由nextHandler來處理
- ConcreteHandler: 具體處理類主要是對具體的處理邏輯和處理的適用條件進行實現。具體處理者接到請求后,可以選擇將請求處理掉,或者將請求傳給下家。由於具體處理者持有對下家的引用,因此,如果需要,具體處理者可以訪問下家
- Client:客戶端
類圖
coding
public abstract class Handler {
private Handler nextHandler;
private int level;
public Handler(int level) {
this.level = level;
}
public void setNextHandler(Handler handler){
this.nextHandler = handler;
}
public final void handlerRequest(Request request){
if(level == request.getLevel()){
this.response(request);
}else{
if (this.nextHandler != null){
this.nextHandler.handlerRequest(request);
}else{
System.out.println("===已經沒有處理器了===");
}
}
}
// 抽象方法,子類實現
public abstract void response(Request request);
}
class Request {
int level = 0;
public Request(int level){
this.level = level;
}
public int getLevel() {
return level;
}
}
public class ConcreteHandler1 extends Handler {
public ConcreteHandler1(int level) {
super(level);
}
@Override
public void response(Request request) {
System.out.println("請求由處理器1進行處理");
}
}
public class ConcreteHandler2 extends Handler {
//...
}
public class ConcreteHandler2 extends Handler {
//...
}
public class Client {
public static void main(String[] args) {
ConcreteHandler1 handler1 = new ConcreteHandler1(1);
ConcreteHandler2 handler2 = new ConcreteHandler2(2);
ConcreteHandler3 handler3 = new ConcreteHandler3(3);
//處理者構成一個環形
handler1.setNextHandler(handler2);
handler2.setNextHandler(handler3);
handler1.handlerRequest(new Request(1));
}
}
實例
當你想要讓一個以上的對象有機會能夠處理某個請求的時候,就是用責任鏈模式。
通過責任鏈模式,你可以為某個請求創建一個對象鏈。每個對象依序檢查此請求,並對其進行處理,或者將它傳給鏈中的下一個對象。
比如
- 程序員要請3天以上的假期,在OA申請,需要直接主管、總監、HR 層層審批后才生效。類似的采購審批、報銷審批。。。
- 美團在外賣營銷業務中資源位展示的邏輯 https://tech.meituan.com/2020/03/19/design-pattern-practice-in-marketing.html
應用
JAVA 中的異常處理機制、JAVA WEB 中 Apache Tomcat 對 Encoding 的處理,Struts2 的攔截器,JSP、Servlet 的 Filter 均是責任鏈的典型應用。
Servlet 中的責任鏈
public final class ApplicationFilterChain implements FilterChain {
private static final ThreadLocal<ServletRequest> lastServicedRequest;
private static final ThreadLocal<ServletResponse> lastServicedResponse;
public static final int INCREMENT = 10;
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
private int pos = 0; //下一個要執行的filter的位置
private int n = 0; //filter個數
private Servlet servlet = null;
public ApplicationFilterChain() {
}
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (Globals.IS_SECURITY_ENABLED) {
final ServletRequest req = request;
final ServletResponse res = response;
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws ServletException, IOException {
ApplicationFilterChain.this.internalDoFilter(req, res);
return null;
}
});
} catch (PrivilegedActionException var7) {
Exception e = var7.getException();
if (e instanceof ServletException) {
throw (ServletException)e;
}
if (e instanceof IOException) {
throw (IOException)e;
}
if (e instanceof RuntimeException) {
throw (RuntimeException)e;
}
throw new ServletException(e.getMessage(), e);
}
} else {
this.internalDoFilter(request, response);
}
}
FilterChain 就是一條過濾鏈。其中每個過濾器(Filter)都可以決定是否執行下一步。過濾分兩個方向,進和出:
進:在把ServletRequest和ServletResponse交給Servlet的service方法之前,需要進行過濾
出:在service方法完成后,往客戶端發送之前,需要進行過濾
Spring MVC 中的責任鏈
Spring MVC 的 diapatcherServlet 的 doDispatch 方法中,獲取與請求匹配的處理器 HandlerExecutionChain
就是用到了責任鏈模式。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null; //使用到了責任鏈模式
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
//責任鏈模式執行預處理方法,其實是將請求交給注冊的攔截器執行
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
//責任鏈執行后處理方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var22) {
//...
} finally {
}
}
- SpringMVC 請求的流程中,執行了攔截器相關方法 interceptor.preHandler 等等
- 在處理 SpringMVC 請求時,使用到職責鏈模式還使用到適配器模式
- HandlerExecutionChain 主要負責的是請求攔截器的執行和請求處理,但是他本身不處理請求,只是將請求分配給鏈上注冊處理器執行,這是職責鏈實現方式,減少職責鏈本身與處理邏輯之間的耦合,規范了處理流程
- HandlerExecutionChain 維護了 HandlerInterceptor 的集合, 可以向其中注冊相應的攔截器
總結
責任鏈模式其實就是一個靈活版的 if…else…語句,它就是將這些判定條件的語句放到了各個處理類中,這樣做的優點是比較靈活了,但同樣也帶來了風險,比如設置處理類前后關系時,一定要特別仔細,搞對處理類前后邏輯的條件判斷關系,並且注意不要在鏈中出現循環引用的問題。
優點:
- 降低耦合度:將請求和處理分開,實現解耦,提高了系統的靈活性。
- 簡化了對象:對象不需要知道鏈的結構
- 良好的擴展性:增加處理者的實現很簡單,只需重寫處理請求業務邏輯的方法。
缺點:
- 從鏈頭發出,直到有處理者響應,在責任鏈比較長的時候會影響系統性能,一般需要在 Handler 中設置一個最大節點數。
- 請求遞歸,調試排錯比較麻煩。
使用場景:
- 有多個對象可以處理同一個請求,具體哪個對象處理該請求由運行時刻自動確定。
- 在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。
- 可動態指定一組對象處理請求。
模式的擴展:
職責鏈模式存在以下兩種情況。
- 純的職責鏈模式:一個請求必須被某一個處理者對象所接收,且一個具體處理者對某個請求的處理只能采用以下兩種行為之一:自己處理(承擔責任);把責任推給下家處理。
- 不純的職責鏈模式:允許出現某一個具體處理者對象在承擔了請求的一部分責任后又將剩余的責任傳給下家的情況,且一個請求可以最終不被任何接收端對象所接收。
參考
《研磨設計模式》
https://wiki.jikexueyuan.com/project/java-design-pattern/chain-responsibility-pattern.html
https://refactoringguru.cn/design-patterns/chain-of-responsibility