本文通過圖書館管理系統中,用戶名校驗、密碼校驗、需要增加問題,每次都要增加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的攔截器中使用責任鏈模式是一個好的選擇,
如果在你的專案業務中需要定義一系列攔截器,那么使用責任鏈模式就是一個比較不錯的選擇,
我已經連續更新了數十篇設計模式博客,推薦你結合學習,
一、設計模式概述
二、設計模式之工廠方法和抽象工廠
三、設計模式之單例和原型
四、設計模式之建造者模式
五、設計模式之代理模式
六、設計模式之配接器模式
七、設計模式之橋接模式
八、設計模式之組合模式
九、設計模式之裝飾器模式
十、設計模式之外觀模式
十一、外觀模式之享元模式
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/502674.html
標籤:其他
