Java設計模式之責任鏈模式
本文通過圖書館管理系統中,用戶名校驗、密碼校驗、需要增加問題,每次都要增加if判斷語句,將其改用責任鏈模式進行鏈式調用,為瞭讓代碼更加的優雅,我們使用之前學過的建造者模式就代碼進行改造。接著我們會介紹責任鏈模式在我們常用的框架中的運用,最後是責任鏈模式的優缺點和應用場景。
讀者可以拉取完整代碼到本地進行學習,實現代碼均測試通過後上傳到碼雲。
一、引出問題
小王給老王打造瞭一套圖書館管理系統,隨著訪問量的不斷增加,老王要求增加訪問的用戶名校驗。
小王說這有何難,說著就在用戶訪問圖書館之前加瞭一層判斷語句,判斷用戶名是否合法。過瞭一段時間後,又給每個用戶頒發瞭一個密碼,就需要在用戶名校驗通過以後校驗密碼。
小王就準備在用戶名的判斷語句後,增加密碼的校驗語句。老王趕忙攔住瞭要繼續更改代碼的小王。如果以後再增加角色校驗、權限校驗、你準備寫多少個判斷語句。
而且你把軟件設計原則中的——開閉原則丟到哪裡去瞭。
你可以考慮使用一種模式,將所有的校驗方法都獨立出來一個類,每一個類隻負責處理各自的校驗邏輯,當前的校驗類通過以後傳遞給下一個校驗類進行處理,這樣每次增加新的邏輯判斷都隻需要增加校驗類就行瞭。
就像是一條流水線,每個類負責處理線上的一個環節。
二、責任鏈模式的概念和使用
實際上,老王提出來的正是行為型設計模式中的——**責任鏈模式。
責任鏈模式正如它的名字一樣,將每個職責的步驟串聯起來執行,並且一個步驟執行完成之後才能夠執行下一個步驟。
從名字可以看出通常責任鏈模式使用鏈表來完成。 因此當執行任務的請求發起時,從責任鏈上第一步開始往下傳遞,直到最後一個步驟完成。
在責任鏈模式當中, 客戶端隻用執行一次流程開始的請求便不再需要參與到流程執行當中,責任鏈上的流程便能夠自己一直往下執行,
客戶端同樣也並不關心執行流程細節,從而實現與流程之間的解耦。
責任鏈模式需要有兩個角色:
抽象處理器(Handler):處理器抽象接口,定義瞭處理請求的方法和執行下一步處理的處理器。
具體處理器(ConcreteHandler):執行請求的具體實現,先根據請求執行處理邏輯,完成之後將請求交給下一個處理器執行。
基於責任鏈模式實現圖書館的用戶名校驗和密碼校驗。
抽象處理器:
/** * @author tcy * @Date 22-08-2022 */ public abstract class Handler { private Handler next; public Handler getNext() { return next; } public void setNext(Handler next) { this.next = next; } public abstract void handle(Object request); }
用戶名校驗處理器:
/** * @author tcy * @Date 23-08-2022 */ public class ConcreteHandlerUsername extends Handler{ @Override public void handle(Object request) { //相應的業務邏輯... System.out.println("用戶名校驗通過. 參數: " + request); //調用鏈路中下一個節點的處理方法 if (getNext() != null) { getNext().handle(request); } } }
密碼校驗器:
/** * @author tcy * @Date 23-08-2022 */ public class ConcreteHandlerPassword extends Handler{ @Override public void handle(Object request) { //相應的業務邏輯... System.out.println("密碼校驗通過. 參數: " + request); //調用鏈路中下一個節點的處理方法 if (getNext() != null){ getNext().handle(request); } } }
客戶端調用:
public class Client { //普通模式---------- public static void main(String[] args) { Handler concreteHandler1 = new ConcreteHandlerUsername(); Handler concreteHandler2 = new ConcreteHandlerPassword(); concreteHandler1.setNext(concreteHandler2); concreteHandler1.handle("用戶名tcy"); } }
用戶名校驗通過. 參數: 用戶名tcy
密碼校驗通過. 參數: 用戶名tcy
這樣我們就實現瞭責任鏈模式,但是這種方式我們註意到,調用方調用的時候手動將兩個處理器set到一起,如果這條鏈路很長的時候,這樣的代碼實在是太不優雅瞭。
將我們曾經學過的設計模式扒出來,看使用哪種模式能讓它看起來更優雅一點。
三、責任鏈模式+建造者模式
我們看建造型設計模式的文章,看建造者模式中的典型應用中的Lombok。
參考Lombok的 @Builder例子,是不是和我們這個有著些許相似呢?
我們在Handle的類中創建一個Builder內部類。
/** * 建造者模式 */ public static class Builder{ private Handler head; private Handler tail; public Builder addHanlder(Handler handler){ //head==null表示第一次添加到隊列 if (null == head){ head = this.tail = handler; return this; } //原tail節點指向新添加進來的節點 this.tail.setNext(handler); //新添加進來的節點設置為tail節點 this.tail = handler; return this; } public Handler build(){ return this.head; } }
該內部類更像是一個鏈表結構,定義一個頭和尾對象,add方法是向鏈接的頭尾中賦值,build返回頭元素方便開始鏈式調用。我們對調用方代碼進行改造。
//建造者模式--------- public static void main(String[] args) { Handler.Builder builder = new Handler.Builder(); builder.addHanlder(new ConcreteHandlerUsername()) .addHanlder(new ConcreteHandlerPassword()); builder.build().handle("用戶名tcy"); }
這樣的實現方式比原方式優雅多瞭。責任鏈模式本身是很簡單的,如果將責任鏈模式和建造者模式組合起來使用就沒那麼容易理解瞭。
在實際使用中往往不是一個單一的設計模式,更多的是多種組合模式組成的“四不像”,實際上這並不是一件輕松的事。
四、責任鏈模式在源碼運用
為瞭加深理解我們繼續深入責任鏈模式在Spring中的運用。
Spring Web 中的 HandlerInterceptor
,裡面有preHandle()
、postHandle()
、afterCompletion()
三個方法,實現這三個方法可以分別在調用"Controller"方法之前,調用"Controller"方法之後渲染"ModelAndView"之前,以及渲染"ModelAndView"之後執行。
public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
HandlerInterceptor
就是角色中的抽象處理者,HandlerExecutionChain相當於上述中的Client,用於調用責任鏈上的各個環節。
public class HandlerExecutionChain { ... @Nullable private HandlerInterceptor[] interceptors; private int interceptorIndex = -1; boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!interceptor.preHandle(request, response, this.handler)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } } return true; } void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); } } } void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } } } }
私有的數組 private HandlerInterceptor[] interceptors 用於存儲責任鏈的每個環節,,然後通過interceptorIndex
作為指針去遍歷責任鏈數組按順序調用處理者。
結合我們上面給出的例子,在Spring中的應用是比較容易理解的。
在Servlet的一系列攔截器也是采用的責任鏈模式,有興趣的讀者可以深入研究一下。
五、總結
當必須按順序執行多個處理者時,可以考慮使用責任鏈模式。如果處理者的順序及其必須在運行時改變時,可以考慮使用責任鏈模式。責任鏈的模式是缺點也很明顯,增加瞭系統的復雜性。
但是要切忌避免過度設計,在實際應用中,校驗用戶名和密碼的業務邏輯並沒有那麼的復雜,可能隻是一個判斷語句,使用設計模式隻會增加系統的復雜性,而在Shiro、SpringSecurity、SpringMVC的攔截器中使用責任鏈模式是一個好的選擇。
如果在你的項目業務中需要定義一系列攔截器,那麼使用責任鏈模式就是一個比較不錯的選擇。
以上就是這篇文章的全部內容瞭,希望本文的內容對大傢的學習或者工作具有一定的參考學習價值,謝謝大傢對WalkonNet的支持。如果你想瞭解更多相關內容請查看下面相關鏈接
推薦閱讀:
- 詳解SpringMVC HandlerInterceptor攔截器的使用與參數
- Java Spring攔截器案例詳解
- 詳解SpringMVC的攔截器鏈實現及攔截器鏈配置
- SpringMVC攔截器創建配置及執行順序
- 一文瞭解Spring中攔截器的原理與使用