多重校驗神器責任鏈模式

背景

最近在做需求,寫一個方法,先在前面做驗證,if 不滿足 A 條件則 return,if 不滿足 B 條件則 return... 一共寫了 5 個驗證,等驗證通過以後才執行下面的邏輯,這個也沒問題。過了一陣產品提了需求,跟這個方法類似,我又把這個方法 copy 了一份,只不過驗證條件稍微有點不一樣,要變成 6 個驗證了。

這時候我就發現有三個問題,第一重複代碼,相同的 A 條件 B 條件 C 條件寫了兩份,沒有複用。第二,“頭重腳輕”,比如 100 行的方法,前面 60 行都是驗證,後面 40 行纔是真正有用的業務代碼,你看一個方法功能的時候前面驗證肯定是不關心的,只看後面 40 行到底在幹什麼邏輯,所以要縮短驗證代碼的行數。第三,先後順序,一個方法 A 是在 B 之前驗證的,另一個方法 A 是在 B 之後驗證的,調整起來很不方便。

這時候我就想到了用**「責任鏈模式」**來進行優化解決。

定義

責任鏈模式(Chain of Responsibility Pattern)是將鏈中每一個節點看作是一個對象,每個節點處理的請求均不同,且內部自動維護一個下一節點對象。當一個請求從鏈式的首端發出時,會沿着鏈的路徑依次傳遞給每一個節點對象,直至有對象處理這個請求爲止。屬於行爲型模式。

生活中的應用場景就是**「審批流」**。責任鏈模式主要是解耦了請求與處理,客戶只需將請求發送到鏈上即可,無需關心請求的具體內容和處理細節,請求會自動進行傳遞直至有節點對象進行處理。

通用 UML 類圖

責任鏈模式

例子

下面寫一個登錄驗證判斷的例子,一般責任鏈模式會搭配着**「建造者模式」**一起用,即**「鏈式編程」**。因爲這樣鏈條看起來更加清晰明瞭,而傳統的寫法很抽象,很難看出誰誰誰在誰的前面,誰誰誰在誰的後面,如下所示:

AAAHandler.setNextHandler(deptManagerLeaveHandler);
directLeaderLeaveHandler.setNextHandler(deptManagerLeaveHandler);
BBBHandler.setNextHandler(AAAHandler);
deptManagerLeaveHandler.setNextHandler(gManagerLeaveHandler);

下面先創建一個 Handler 的抽象類,這個類裏面有一個下一個 Handler 處理器 next,還有一個 Builder,這個就是用來構建鏈的,也是方便我們的鏈式編程。

public abstract class Handler<T> {
    protected Handler next;
    private void next(Handler next) {
        this.next = next;
    }
    public abstract void doHandler(Member member);
    public static class Builder<T> {
        private Handler<T> head;
        private Handler<T> tail;
        public Builder<T> addHandler(Handler handler) {
            if (this.head == null) {
                this.head = this.tail = handler;
                return this;
            }
            this.tail.next(handler);
            this.tail = handler;
            return this;
        }
        public Handler<T> build() {
            return this.head;
        }
    }
}

下面寫非空校驗 ValidateHandler 類,這裏面先判斷用戶名和密碼是否爲空,空的話返回,非空的話判斷 next 是否爲空,非空的話就丟給下一個處理器去執行。

public class ValidateHandler extends Handler {
    @Override
    public void doHandler(Member member) {
        if (StringUtils.isEmpty(member.getUsername()) ||
                StringUtils.isEmpty(member.getPassword())) {
            System.out.println("用戶名和密碼不能爲空");
            return;
        }
        if (null != next) {
            next.doHandler(member);
        }
    }
}

創建登錄檢驗 LoginHandler 類,判斷賬號密碼是否正確

public class LoginHandler extends Handler {
    @Override
    public void doHandler(Member member) {
        if (!"jack".equals(member.getUsername()) || !"666".equals(member.getPassword())) {
            System.out.println("用戶名密碼不正確");
            return;
        }
        if (null != next) {
            next.doHandler(member);
        }
    }
}

創建權限檢驗 AuthHandler 類,判斷角色是否有權限

public class AuthHandler extends Handler {
    @Override
    public void doHandler(Member member) {
        if (!"管理員".equals(member.getRoleName())) {
            System.out.println("您不是管理員,沒有操作權限");
            return;
        }
        if (null != next) {
            next.doHandler(member);
        }
    }
}

創建執行業務邏輯類

public class BusinessLogicHandler extends Handler {
    @Override
    public void doHandler(Member member) {
        System.out.println("執行業務邏輯。。");
    }
}

好,下面寫個測試類來測試一下

public class Test {
    public static void main(String[] args) {
        Handler.Builder builder = new Handler.Builder();
        //這裏就是鏈式編程,誰在前誰在後看的清清楚楚,明明白白
        builder.addHandler(new ValidateHandler())
                .addHandler(new LoginHandler())
                .addHandler(new AuthHandler())
                .addHandler(new BusinessLogicHandler());
        Member member = new Member();
        member.setUsername("");
        member.setPassword("");
        builder.build().doHandler(member);
    }
}

執行一下,提示用戶名密碼不能爲空

修改下用戶名和密碼

執行一下,提示用戶名密碼不正確

直到把用戶名密碼權限都設置正確

此時所有驗證都通過,開始執行業務邏輯了

源碼中的應用

我們來看一個 J2EE 標準中非常常見的 Filter 類:

public interface Filter {
    public default void init(FilterConfig filterConfig) throws ServletException {}
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
    public default void destroy() {}
}

這個 Filter 接口的定義非常簡單,相當於責任鏈模型中的 handler 抽象角色,我們來看 Spring 中的實現 MockFilterChain 類:

public class MockFilterChain implements FilterChain {
   @Nullable
   private ServletRequest request;
   @Nullable
   private ServletResponse response;
   private final List<Filter> filters;
   @Nullable
   private Iterator<Filter> iterator;
   public MockFilterChain() {
      this.filters = Collections.emptyList();
   }
   public MockFilterChain(Servlet servlet) {
      this.filters = initFilterList(servlet);
   }
   public MockFilterChain(Servlet servlet, Filter... filters) {
      Assert.notNull(filters, "filters cannot be null");
      Assert.noNullElements(filters, "filters cannot contain null values");
      this.filters = initFilterList(servlet, filters);
   }
   private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
      Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
      return Arrays.asList(allFilters);
   }
   @Nullable
   public ServletRequest getRequest() {
      return this.request;
   }
   @Nullable
   public ServletResponse getResponse() {
      return this.response;
   }
   @Override
   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 = this.iterator.next();
         nextFilter.doFilter(request, response, this);
      }
      this.request = request;
      this.response = response;
   }
   public void reset() {
      this.request = null;
      this.response = null;
      this.iterator = null;
   }
       ...
}

這裏面把鏈條中所有的 Filter 都放到List<Filter> filters中, 在 doFilter() 方法中有一段核心的代碼this.iterator.hasNext(),這個就相當於 for 循環的執行 filters 中的 Filter 方法。「雖然寫法不同,但也起到了責任鏈的功能,所以在學習設計模式中,不要拘泥於標準的寫法,很多都是變種的,或者寫着寫着四不像的模式,既像這個設計模式,又像那個設計模式,這個很正常,能起到精簡代碼,高效運行的都是好代碼。」

優缺點

優點:

  1. 將請求與處理解耦。

  2. 請求處理者(節點對象)只需關注自己感興趣的請求進行處理,對於不感興趣的請求,直接轉發給下一級節點對象。

  3. 具備鏈式傳遞處理請求功能,請求發送者無需知曉鏈路結構,只需等待請求處理結果。

  4. 鏈路結構靈活,可以通過改變鏈路結構動態地新增或刪減責任。

  5. 易於擴展新的請求處理類(節點),符合開閉原則。

缺點:

  1. 責任鏈太長或者處理時間過長,會影響整體性能。

  2. 如果節點對象存在循環引用時,會造成死循環,導致系統崩潰。

作者:小杰博士

來源:https://juejin.cn/post/7011490664714240008

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/o0j82bQ8VMlMsWAd-DmB1A