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

 

定義

責任鏈模式是一種設計模式。在責任鏈模式裏,不少對象由每個對象對其下家的引用而鏈接起來造成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。html

發出這個請求的客戶端並不知道鏈上的哪個對象最終處理這個請求,這使得系統能夠在不影響客戶端的狀況下動態地從新組織和分配責任。設計模式

 

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

責任鏈類圖

 

 

責任鏈模式涉及到的角色以下所示安全

● 抽象處理者(BaseHandler)角色定義出一個處理請求的接口。若是須要,接口能夠定義出一個方法以設定和返回對下家的引用。ide

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

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

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

因爲具體處理者持有對下家的引用,所以,若是須要,具體處理者能夠訪問下家。spa

 

源代碼

抽象處理者(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

相關文章
相關標籤/搜索