一、问题引入
在生活中,我们会遇到填写调查问卷的情况,比如中国移动推送的通话质量问卷、京东的购物体验问卷等等,这些问卷在生成之前往往会有一套复杂的逻辑,比如题目的跳转设置、不同题目之间的互斥设置、多选题的选项之间互斥设置,以及对答案的通过性判断等等。在这些背后,某些业务的实现就可以使用到本文所介绍的责任链模式,本文也将以保存用户答题作为模拟实例引入责任链模式。
二、责任链设计模式理论知识
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 }
我们启动,测试结果为:
调用成功,任务按照我们的预期依次顺序执行。