一、問題引入
在生活中,我們會遇到填寫調查問卷的情況,比如中國移動推送的通話質量問卷、京東的購物體驗問卷等等,這些問卷在生成之前往往會有一套復雜的邏輯,比如題目的跳轉設置、不同題目之間的互斥設置、多選題的選項之間互斥設置,以及對答案的通過性判斷等等。在這些背后,某些業務的實現就可以使用到本文所介紹的責任鏈模式,本文也將以保存用戶答題作為模擬實例引入責任鏈模式。
二、責任鏈設計模式理論知識
2.1,責任鏈概念
顧名思義,責任鏈模式(Chain of Responsibility Pattern)為請求創建了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。這種類型的設計模式屬於行為模式。
它的意圖的是:避免請求發送者與接收者耦合在一起,讓多個對象都有可能接收請求,將這些對象連接成一條鏈,並且沿着這條鏈傳遞請求,直到有對象處理它為止。主要解決的問題是:職責鏈上的處理者負責處理請求,客戶只需要將請求發送到職責鏈上即可,無須關心請求的處理細節和請求的傳遞,所以職責鏈將請求的發送者和請求的處理者解耦了。
從概念中我們可以知道,責任鏈模式的核心思想是,按照設計好的有序鏈條逐個自動執行每一個任務。這種設計模式在分類上屬於行為設計模式。
2.2,責任鏈類圖
2.3,鏈的實現方式
責任鏈模式中的鏈,可以使用單向鏈表、List集合實現。個人感覺,單項鏈表在每個節點中包含下個節點的引用,在使用起來會比較方便,而且穩定。
三、責任鏈設計模式的應用
保存答題的具體場景為:先保存答題者,然后每個答題者可以回答多個問卷,所以答題者保存完成之后需要保存回答的是哪個答卷,最后保用戶的答案。
我們用respondent單詞表示答題者,用questionnaire表示答卷,用answer表示答案,在下面的代碼實例中可根據單詞的直譯表示類的作用
下面將用實際的代碼例子演示如何實現責任鏈,且默認使用的是SpringBoot框架。
首先我們創建責任鏈的處理類:RespondChainHandler
1 /** 2 * @Author Administrator 3 * @Date 2021-02-17 15:30:01 4 * @Version 1.0 5 * @Description 責任鏈模式的具體執行handler 6 */ 7 public abstract class RespondChainHandler { 8 /** 9 * 節點排序字段 10 * */ 11 private int order; 12 13 /** 14 * 下一個節點 15 * */ 16 private RespondChainHandler next; 17 18 /** 19 * 執行具體任務 20 * 21 * @param chainEntity 任務數據 22 */ 23 protected abstract void doHandler(ChainEntity chainEntity); 24 25 26 public int getOrder() { 27 return order; 28 } 29 30 public void setOrder(int order) { 31 this.order = order; 32 } 33 34 public RespondChainHandler getNext() { 35 return next; 36 } 37 38 public void setNext(RespondChainHandler next) { 39 this.next = next; 40 } 41 }
然后創建責任鏈的核心類,即責任鏈調用類:RespondChain
1 /** 2 * @Author Administrator 3 * @Date 2021-02-17 15:29:39 4 * @Version 1.0 5 * @Description 責任鏈模式執行保存答題任務 6 */ 7 public class RespondChain { 8 /** 9 * 頭節點 10 * */ 11 private RespondChainHandler header; 12 13 /** 14 * 任務執行入口 15 * 16 * @param chainEntity 數據 17 */ 18 public void proceed(ChainEntity chainEntity) { 19 RespondChainHandler respond = header; 20 while (respond != null) { 21 respond.doHandler(chainEntity); 22 respond = respond.getNext(); 23 } 24 } 25 26 /** 27 * 添加具體任務handler到單向鏈表 28 * 29 * @param respond 任務handler 30 * @param order 排序,越小越靠前 31 */ 32 public void addFilter(RespondChainHandler respond, int order) { 33 respond.setOrder(order); 34 35 if (header == null) { 36 header = respond; 37 respond.setNext(null); 38 } else if (respond.getOrder() <= header.getOrder()) {//如果當前插入的排序小於header的排序,則插入到鏈表的頭 39 //插入到鏈表的隊首位置 40 respond.setNext(header); 41 header = respond; 42 } else {//插入到中間某一個位置 43 RespondChainHandler previous = header; 44 RespondChainHandler current = previous.getNext(); 45 //尋找鏈表中符合當前order排序的位置 46 while (current != null) { 47 if (respond.getOrder() <= current.getOrder()) { 48 previous.setNext(respond); 49 respond.setNext(current); 50 break; 51 } else { 52 previous = current; 53 current = previous.getNext(); 54 } 55 } 56 //隊尾 57 if (current == null) { 58 respond.setNext(null); 59 previous.setNext(respond); 60 } 61 } 62 } 63 }
創建責任鏈處理的數據類:ChainEntity(這里名字起的不好,或許用DTO表示會更清晰)
1 /** 2 * @Author Administrator 3 * @Date 2021-02-17 15:32:36 4 * @Version 1.0 5 * @Description 責任鏈需要處理的數據 6 */ 7 @Data 8 public class ChainEntity { 9 //示例字段 10 private Integer id; 11 //示例字段 12 private String str1; 13 //示例字段 14 private String str2; 15 //示例字段 16 private List<Question> questions; 17 18 @Data 19 public static class Question { 20 //示例字段 21 private Long questionId; 22 //示例字段 23 private String questionName; 24 //示例字段 25 private List<Answer> answers; 26 27 @Data 28 public static class Answer{ 29 //示例字段 30 private Long itemId; 31 //示例字段 32 private String itemContent; 33 } 34 } 35 }
處理類:RespondChainHandler是一個抽象類,具體的任務處理處理類要繼承該類。RespondChainHandler處理類中有兩個關鍵的地方:order和next,order用於加入單向鏈表時排序使用,next指向的是下一個節點。
調用類:RespondChain,header是單向鏈表的頭節點,processd是任務執行入口,其中參數ChainEntity是外部傳入的數據,作為責任鏈要處理的數據的載體。processd方法從header開始,先執行header節點里的doHandler任務,然后指向next節點,用while循環執行下去,直到沒有更多的next節點。
下面我們創建具體的任務子類:
創建保存答題者任務子類:SaveRespondentClient
1 /** 2 * @Author Administrator 3 * @Date 2021-02-17 15:30:22 4 * @Version 1.0 5 * @Description 保存答題者任務 6 */ 7 @Component 8 public class SaveRespondentClient extends RespondChainHandler { 9 10 @Override 11 protected void doHandler(ChainEntity chainEntity) { 12 System.out.println("保存答題者任務完成..."); 13 } 14 }
創建保存答卷任務子類:SaveQuestionnaireClient
1 /** 2 * @Author Administrator 3 * @Date 2021-02-17 15:30:45 4 * @Version 1.0 5 * @Description 保存答卷任務 6 */ 7 @Component 8 public class SaveQuestionnaireClient extends RespondChainHandler { 9 10 @Override 11 protected void doHandler(ChainEntity chainEntity) { 12 System.out.println("保存答卷任務完成..."); 13 } 14 }
創建保存答案任務子類:SaveAnswerClient
1 /** 2 * @Author Administrator 3 * @Date 2021-02-17 15:31:00 4 * @Version 1.0 5 * @Description 保存答案任務 6 */ 7 @Component 8 public class SaveAnswerClient extends RespondChainHandler { 9 10 @Override 11 protected void doHandler(ChainEntity chainEntity) { 12 System.out.println("保存答案任務完成..."); 13 } 14 }
這三個子類處理自己職責范圍內的事情。
然后我們創建外部調用類,處理保存答題業務,外部調用類用Controller模擬。我們默認使用的是SpringBoot框架,所以可以不用new對象,使用IOC容器即可。如果不使用SpringBoot當然是可以的,不過要記得將類實例化。
1 /** 2 * @Author Administrator 3 * @Date 2021-02-17 15:37:08 4 * @Version 1.0 5 * @Description 責任鏈模式測試controller 6 */ 7 @RestController 8 @RequestMapping("/chain") 9 public class ChainController { 10 //從IOC容器中取出處理類映射成Map,Map的key是處理類的類名,value是已實例化的子類 11 @Resource 12 private Map<String, RespondChainHandler> respondChainHandlerMap; 13 14 @GetMapping(value = "save") 15 public String save(){ 16 ChainEntity chainEntity =new ChainEntity(); 17 RespondChain respondChain = new RespondChain(); 18 respondChain.addFilter(respondChainHandlerMap.get("saveRespondentClient"), 1); 19 respondChain.addFilter(respondChainHandlerMap.get("saveQuestionnaireClient"), 2); 20 respondChain.addFilter(respondChainHandlerMap.get("saveAnswerClient"), 3); 21 //開始執行 22 respondChain.proceed(chainEntity); 23 24 return "執行完成"; 25 } 26 }
我們啟動,測試結果為:
調用成功,任務按照我們的預期依次順序執行。