設計模式(三) 責任鏈模式

一抹微笑~發表於2019-07-18

 

定義

責任鏈模式是一種設計模式。在責任鏈模式裡,很多物件由每一個物件對其下家的引用而連線起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個物件決定處理此請求。

發出這個請求的客戶端並不知道鏈上的哪一個物件最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。

 

簡而言之,就是將多個物件以鏈條的形式進行連線。每一個物件都會引用下一個物件。
請求在鏈條上進行傳遞,直到某個物件處理了請求,傳遞終止。

責任鏈類圖

 

 

責任鏈模式涉及到的角色如下所示

● 抽象處理者(BaseHandler)角色定義出一個處理請求的介面。如果需要,介面可以定義出一個方法以設定和返回對下家的引用。

這個角色通常由一個Java抽象類或者Java介面實現。上圖中Handler類的聚合關係給出了具體子類對下家的引用,

抽象方法handleRequest()規範了子類處理請求的操作。

● 具體處理者(ConcreteHandler)角色:具體處理者接到請求後,可以選擇將請求處理掉,或者將請求傳給下家。

由於具體處理者持有對下家的引用,因此,如果需要,具體處理者可以訪問下家。

 

原始碼

抽象處理者(Handler)角色

 

定義了一個物件,四個方法:

successor:持有當前責任的物件。
getSuccessor():獲取下家責任物件的方法。
setSuccessor():設定下家責任物件的方法。
handlerRequest():處理當前請求的方法。如果當前物件可以處理請求,處理請求。如果當前物件不可以處理請求,傳遞給下家。

 

 1 public abstract class BaseHandler {
 2 
 3     /**持有後續的責任物件*/
 4     protected BaseHandler successor;
 5 
 6     /**
 7      * 示意處理請求的方法,雖然這個示意方法並沒有傳入引數
 8      * 但是實際是可以傳入引數的,根據實際情況進行選擇
 9      */
10     public abstract void handleRequest();
11 
12     /**
13      * 獲取後續的責任物件
14      */
15     public BaseHandler getSuccessor() {
16         return successor;
17     }
18 
19     /**
20      * 設定後續的責任物件
21      */
22     public void setSuccessor(BaseHandler successor) {
23         this.successor = successor;
24     }
25 }

 

 

具體處理者(ConcreteHandler)角色

繼承BaseHandler類,重寫了handleRequest()方法。
如果當前的處理物件有下家就傳遞給下家,沒有下家就自行處理請求。
通過getSuccessor().handleRequest()可以引用下家。

 1 public class ConcreteHandler extends BaseHandler {
 2 
 3 
 4     /**處理器名稱*/
 5     private String handlerName;
 6     public ConcreteHandler(String handlerName) {
 7         this.handlerName = handlerName;
 8     }
 9 
10     /**
11      * 處理請求的方法
12      * 如果當前物件有下家,就傳遞給下家
13      * 如果當前物件沒有下家,就自行處理
14      */
15     @Override
16     public void handleRequest() {
17 
18         if(getSuccessor() !=null){
19             System.out.println("已經有物件處理此請求!"+this.handlerName);
20             getSuccessor().handleRequest();
21         }else {
22             System.out.println("正在處理請求......."+this.handlerName);
23         }
24 
25         System.out.println(" ======= 請求處理結束 ======= "+new Date().toLocaleString());
26     }
27 }

 

 

測試用例

 1     public static void main(String[] args) {
 2 
 3         BaseHandler baseHandler1=new ConcreteHandler("baseHandler1");
 4         BaseHandler baseHandler2=new ConcreteHandler("baseHandler2");
 5         BaseHandler baseHandler3=new ConcreteHandler("baseHandler3");
 6         BaseHandler baseHandler4=new ConcreteHandler("baseHandler4");
 7         baseHandler1.setSuccessor(baseHandler2);
 8         baseHandler2.setSuccessor(baseHandler3);
 9         baseHandler3.setSuccessor(baseHandler4);
10 
11         //提交請求
12         baseHandler1.handleRequest();
13 
14     }

 

 

場景一聚餐費用 

 

一般申請聚餐費用的流程為:申請人填寫申請表->提交給領導審批
->如果領導批准->去財務領錢
->如果領導不批准->就沒有然後了

 

不同的級別領導的審批額度不一樣
比如:專案經理審批最大額度為2000,部門經理審批最大額度為5000,總經理審批最大額度為50000。
也就是說如果審批的費用在2000以內,專案經理、部門經理、總經理都可以審批。如果審批的費用在2000以上,只有部門經理、總經理可以審批。如果審批的費用在5000以上,只有總經理可以審批。

 

但是最終那個領導進行審批,請求人是不清楚的。這個流程就可以使用責任鏈模式來實現。

 

使用者提交申請,請求會在(專案經理審批-部門經理審批-總經理審批)這樣一條責任處理鏈上傳遞。直到某個領導處理了這個請求,這個傳遞才會結束。

 

思路:因為每個領導的審批額度是不一樣的,所以每個領導應該分別建一個單獨的物件,處理各自的業務。而每個領導都繼承了同一個抽象的父類,擁有共同的行為和方法。方便根據不同的功能選擇對應的審批流程。

 

原始碼

審批處理抽象父類(BaseApprovalHandler)

 

定義了一個物件,四個方法。
baseApprovalHandler:持有當前責任的物件
transferRequest():負責將請求傳遞給下家
getBaseApprovalHandler():獲取當前的責任物件
setBaseApprovalHandler():設定當前的責任物件
handlerRequest():處理當前請求的方法。如果當前物件可以處理請求,處理請求。如果當前物件不可以處理請求,傳遞給下家。

 

 

 1 public abstract class BaseApprovalHandler {
 2 
 3 
 4     /**
 5      * 審批當前請求的責任物件
 6      */
 7     protected BaseApprovalHandler baseApprovalHandler;
 8 
 9     /**
10      * 處理當前請求的方法
11      * 如果當前物件可以處理請求->處理請求
12      * 如果當前物件不可以處理請求->傳遞給下家
13      *
14      * @param money 審批金額
15      */
16     public abstract void handlerRequest(int money);
17 
18     /**
19      * 將請求傳遞給下家
20      */
21     protected void transferRequest(int money){
22 
23         //將請求傳遞給下家
24         BaseApprovalHandler baseApprovalHandler = getBaseApprovalHandler();
25         if (baseApprovalHandler != null) {
26             baseApprovalHandler.handlerRequest(money);
27         }
28     }
29 
30     /**
31      * 獲取當前的責任物件
32      */
33     public BaseApprovalHandler getBaseApprovalHandler() {
34         return baseApprovalHandler;
35     }
36 
37     /**
38      * 設定當前的責任物件
39      */
40     public void setBaseApprovalHandler(BaseApprovalHandler baseApprovalHandler) {
41         this.baseApprovalHandler = baseApprovalHandler;
42     }
43 }

 

 

專案經理(ProjectManager)

職能:只能審批N元以下的申請,超出職能傳遞給BranchManager

 1 public class ProjectManager extends BaseApprovalHandler {
 2 
 3 
 4     public ProjectManager(BaseApprovalHandler baseApprovalHandler) {
 5         this.baseApprovalHandler=baseApprovalHandler;
 6     }
 7 
 8     @Override
 9     public void handlerRequest(int money) {
10 
11         //最大審批額度
12         int maxMoney=2000;
13 
14         if(money <=maxMoney){
15             System.out.println("我是"+this.getClass().getSimpleName()+",正在審批金額 money="+money);
16         }else {
17             System.out.println("我是"+this.getClass().getSimpleName()+",金額超出我的審批範圍 money="+money);
18 
19             //傳遞請求
20             transferRequest(money);
21 
22         }
23 
24     }
25 }

 

 

部門經理(BranchManager)

職能:只能審批N元以下的申請,超出職能傳遞給GeneralManager

 1 public class BranchManager extends BaseApprovalHandler {
 2 
 3 
 4     public BranchManager(BaseApprovalHandler baseApprovalHandler) {
 5         this.baseApprovalHandler = baseApprovalHandler;
 6     }
 7 
 8     @Override
 9     public void handlerRequest(int money) {
10 
11         //最大審批額度
12         int maxMoney=5000;
13 
14         if(money <=maxMoney){
15             System.out.println("我是"+this.getClass().getSimpleName()+",正在審批金額 money="+money);
16         }else {
17             System.out.println("我是"+this.getClass().getSimpleName()+",金額超出我的審批範圍 money="+money);
18 
19             //傳遞請求
20             transferRequest(money);
21         }
22 
23     }
24 }

 

 

 

總經理(GeneralManager)

職能:只能審批N元以下的申請,超出職能直接拒絕

 1 public class GeneralManager extends BaseApprovalHandler {
 2 
 3 
 4     public GeneralManager(BaseApprovalHandler baseApprovalHandler) {
 5         this.baseApprovalHandler = baseApprovalHandler;
 6     }
 7 
 8     @Override
 9     public void handlerRequest(int money) {
10 
11         //最大審批額度
12         int maxMoney=50000;
13 
14         if(money <=maxMoney){
15             System.out.println("我是"+this.getClass().getSimpleName()+",正在審批金額 money="+money);
16         }else {
17             System.out.println("我是"+this.getClass().getSimpleName()+",金額過大無法審批 money="+money);
18 
19             //傳遞請求
20             transferRequest(money);
21         }
22 
23     }
24 }

 

 

處理工廠(HandlerFactory)

作用:規範審批流程。當前情景的審批需要先經過專案經理,專案經理能審批就處理,不能審批傳遞給部門經理。
部門經理能審批就處理不能審批就傳遞給總經理。以此類推

 1 public class HandlerFactory {
 2 
 3     /**
 4      * 審批處理流程
 5      * 專案經理->部門經理->總經理
 6      */
 7  8     public static BaseApprovalHandler getProjectManagerHandler() {
 9         return new ProjectManager(new BranchManager(new GeneralManager(null)));
10     }
11     
12 }

 

 

測試用例

1     public static void main(String[] args) {
2 
3         //申請金額
4         int money = 8000;
5 
6         System.out.println("\n\n專案經理開始審批");
7         HandlerFactory.getProjectManagerHandler().handlerRequest(money);
8     }

 

 

擴充套件性

如果此時審批流程沒有專案經理這個角色了。新增了兩個審批流程:(部門經理審批->總經理審批)、(總經理審批)。應該怎麼辦?
這個時候工廠的作用就出現了。我們可以新增兩個方法getBranchManagerHandler()、getGeneralManagerHandler()。然後將以前的getProjectManagerHandler()方法暫時廢棄掉即可。

 1 public class HandlerFactory {
 2 
 3     /**
 4      * 審批處理流程
 5      * 專案經理->部門經理->總經理
 6      */
 7     @Deprecated
 8     public static BaseApprovalHandler getProjectManagerHandler() {
 9         return new ProjectManager(new BranchManager(new GeneralManager(null)));
10     }
11 
12     /**
13      * 審批處理流程
14      * 部門經理->總經理
15      */
16     public static BaseApprovalHandler getBranchManagerHandler() {
17         return new BranchManager(new GeneralManager(null));
18     }
19 
20     /**
21      * 審批處理流程
22      * 總經理
23      */
24     public static BaseApprovalHandler getGeneralManagerHandler() {
25         return new GeneralManager(null);
26     }
27 
28 }

 

場景二攔截器應用

 

假設我需要對外提供API服務。考慮到介面安全性需要做token校驗,日誌收集需要做日誌記錄。這個時候怎麼做呢?
一般的時候我們都會選擇加一個攔截器。先做一個token校驗,然後再做一個日誌收集。
或者增加一個日誌攔截器、token攔截器。
那麼我們分析一下這樣做好不好。第一種情況將所有的業務冗餘一個方法一個類,如果以後增加介面限流,增加其他的功能會繼續冗餘。第二種情況增加多個攔截器,以後增加功能攔截器會越來越多。能不能做到有效的管理?

 

思路:我們知道責任鏈的功能是把多個業務功能進行串聯。當請求到達token校驗類的時候校驗token,檢驗完token傳遞給下家。當請求到達日誌類的時候做日誌記錄,做完日誌記錄傳遞給下家。那麼,如果獲取這個請求呢?我們可以增加一個攔截器,攔截器接收到請求之後,將請求傳遞到責任鏈。
這樣一來,只需要一個攔截器我們就完成了token校驗、日誌收集。如果後期增加功能只需要增加一個類即可。

 

檢視以下類圖

 

 

首先客戶端接收到請求會進入攔截器(BaseFilter),在doFilter()方法中進行接收。doFilter()獲取request引數、response引數。並呼叫BaseFilterFactory工廠將request引數、response引數傳到責任鏈。
此時責任連結接收到request引數、response引數。會進行token校驗、日誌採集,執行完會呼叫下家繼續執行,如果沒有下家則停止傳遞。

 

原始碼

審批處理抽象父類(BaseFilterHandler)

 

定義了一個物件,四個方法
successor:持有當前責任的物件
transferRequest():負責將請求傳遞給下家
getBaseApprovalHandler():獲取當前的責任物件
setBaseApprovalHandler():設定當前的責任物件
handlerRequest():處理當前請求的方法。如果當前物件可以處理請求,處理請求。如果當前物件不可以處理請求,傳遞給下家。

 

  

 1 public abstract class BaseFilterHandler {
 2 
 3     /** 處理當前請求的責任物件 */
 4     protected BaseFilterHandler successor;
 5 
 6     /**
 7      * 處理請求
 8      *
 9      * @param request
10      *         請求物件
11      * @param response
12      *         請求物件
13      * @param chain
14      *         決定攔截器是否執行
15      *
16      * @throws IOException
17      *         io異常
18      * @throws ServletException
19      *         servlet異常
20      */
21     public abstract void handlerRequest(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
22 
23     /**
24      * 將請求傳遞給下家
25      *
26      * @param request
27      *         請求物件
28      * @param response
29      *         請求物件
30      * @param chain
31      *         決定攔截器是否執行
32      *
33      * @throws IOException
34      *         io異常
35      * @throws ServletException
36      *         servlet異常
37      */
38     protected void transferRequest(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
39 
40         //將請求傳遞給下家
41         BaseFilterHandler baseFilter = getSuccessor();
42         if (baseFilter != null) {
43             baseFilter.handlerRequest(request, response, chain);
44         }
45     }
46 
47 
48     public BaseFilterHandler getSuccessor() {
49         return successor;
50     }
51 
52     public void setSuccessor(BaseFilterHandler successor) {
53         this.successor = successor;
54     }
55 }

 

 

日誌記錄(LogRecord)

職能:負責獲取介面請求與引數,進行統一日誌記錄管理

 1 public class LogRecord extends BaseFilterHandler {
 2 
 3     public LogRecord(BaseFilterHandler baseFilterHandler) {
 4         this.successor = baseFilterHandler;
 5     }
 6 
 7     @Override
 8     public void handlerRequest(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
 9 
10 
11         System.out.println("\n 日誌記錄攔截器..................");
12 
13         //日誌記錄->記錄完日誌呼叫下家
14         HttpServletRequest httpServletRequest = (HttpServletRequest) request;
15 
16 
17         //獲取請求URL
18         StringBuffer requestURL = httpServletRequest.getRequestURL();
19         System.out.println("請求URL: requestURL=" + requestURL);
20 
21         //獲取請求引數
22         Map<String, String[]> parameterMap = request.getParameterMap();
23         if (parameterMap.size() > 0) {
24 
25             Map<String, String> parameterMap2 = new HashMap<>(parameterMap.size());
26             parameterMap.forEach((key, value) -> parameterMap2.put(key, Arrays.toString(value)));
27 
28             System.out.println("請求引數:parameterMap=" + parameterMap2);
29         }
30 
31 
32         //請求傳遞給下家
33         transferRequest(request, response ,chain);
34 
35         chain.doFilter(request, response);
36     }
37 }

 

 

token鑑權(TokenAuth)

職能:負責校驗介面token是否合法

public class TokenAuth extends BaseFilterHandler {

    private final String ckToken = "123";

    public TokenAuth(BaseFilterHandler baseFilterHandler) {
        this.successor = baseFilterHandler;
    }

    @Override
    public void handlerRequest(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        //token校驗->校驗完token呼叫下家

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        System.out.println("\n token校驗攔截器..................");

        //獲取請求引數
        boolean isNext = false;
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap.size() > 0) {

            String[] tokens = parameterMap.get("token");
            if (tokens.length > 0) {
                for (String token : tokens) {
                    if (StringUtils.isNotEmpty(token) && ckToken.equals(token)) {
                        isNext = true;
                    }
                }
            }
        }


        if (isNext) {

            //請求傳遞給下家
            transferRequest(request, response, chain);

            chain.doFilter(request, response);

        } else {
            //這句話的意思,是讓瀏覽器用utf8來解析返回的資料
            httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8");
            //這句話的意思,是告訴servlet用UTF-8轉碼,而不是用預設的ISO8859
            httpServletResponse.setCharacterEncoding("UTF-8");

            String printStr="無效的token";
            httpServletResponse.getWriter().print(printStr);
            System.out.println(printStr);
        }

    }
}

 


處理工廠(BaseFilterFactory)

作用:規範審批流程。當前的執行流程為:token鑑權(TokenAuth)->日誌記錄(LogRecord)。
當然執行順序可以按照業務場景,自行調整。

1 public class BaseFilterFactory {
2 
3     public static BaseFilterHandler getBaseFilterHandler(){
4         return new TokenAuth(new LogRecord(null));
5     }
6 
7 }

 

 

過濾器(BaseFilter)
作用:負責攔截請求,呼叫處理工廠,並將請求引數傳遞給責任鏈

 1 @WebFilter(filterName = "baseFilter", urlPatterns = "/*")
 2 public class BaseFilter implements Filter {
 3 
 4     private final List<String> passUrlList =new ArrayList<>(50);
 5 
 6     @Override
 7     public void init(FilterConfig filterConfig) throws ServletException {
 8         passUrlList.clear();
 9         passUrlList.add(".");
10         passUrlList.add("/login");
11         passUrlList.add("/jiankong");
12     }
13 
14     @Override
15     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
16 
17         System.out.println("\n\n ========================= 執行自定義責任鏈攔截器 ========================= ");
18 
19         //獲取請求URL
20         HttpServletRequest httpServletRequest = (HttpServletRequest) request;
21         String requestURI = httpServletRequest.getRequestURI();
22         if(passUrlList.size() >0){
23             for (String url : passUrlList) {
24                 if (requestURI.contains(url)) {
25 
26                     System.out.println("url ="+requestURI +"放行!");
27 
28                     chain.doFilter(request, response);
29                     return;
30                 }
31             }
32         }
33 
34         //執行自定義責任鏈攔截器
35         BaseFilterHandler baseFilterHandler = BaseFilterFactory.getBaseFilterHandler();
36         baseFilterHandler.handlerRequest(request,response,chain);
37 
38     }
39 }

 

 

配置(BaseFilterConfig)

 1 @Configuration
 2 public class BaseFilterConfig {
 3     @Bean
 4     public FilterRegistrationBean filterRegistrationBean(){
 5         FilterRegistrationBean bean = new FilterRegistrationBean();
 6         bean.setFilter(new BaseFilter());
 7         bean.addUrlPatterns("/*");
 8         return bean;
 9     }
10 }

 

總結

純與不純的責任鏈模式

我們知道責任鏈中有兩個行為:一個是承擔責任,一個是把責任推給下家。

純的責任鏈模式:要求兩個行為中只執行一個,要麼承擔責任,要麼把責任推給下家。不能存在既承擔部分責任,又把責任推給下家的情況。

不純的責任鏈模式:就是即承擔部分責任,又把責任推給下家。當然也有可能出現沒有物件承擔責任的情況。

在抽象處理者(Handler)角色中,我們採用的是純的責任鏈模式,但是這種情況在現實生活中很難找到。
像場景一聚餐費用 與場景二攔截器應用現實生活中均是不純的責任鏈模式。

 

責任鏈模式的應用場景

閘道器:介面鑑權、日誌記錄、介面限流等等
多條件(if else 、switch)流程判斷、許可權控制
ERP系統 流程審批 總經理、人事經理、專案經理
Java過濾器的底層實現Filter

 

相關文章