首頁 > 軟體

Java設計模式之責任鏈模式

2022-10-04 14:00:08

本文通過圖書館管理系統中,使用者名稱校驗、密碼校驗、需要增加問題,每次都要增加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的攔截器中使用責任鏈模式是一個好的選擇。

如果在你的專案業務中需要定義一系列攔截器,那麼使用責任鏈模式就是一個比較不錯的選擇。

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對it145.com的支援。如果你想了解更多相關內容請檢視下面相關連結


IT145.com E-mail:sddin#qq.com