Java设计模式13 - 责任链模式

hiram-QI 2020-11-09 16:06:22
java 设计 模式 责任


责任链模式的定义与特点

责任链模式的定义:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止

标准的责任链模式,个人总结下来有如下几个特点:

  • 链上的每个对象都有机会处理请求
  • 链上的每个对象都持有下一个要处理对象的引用
  • 链上的某个对象无法处理当前请求,那么它会把相同的请求传给下一个对象

用一张图表示以下使用了责任链模式之后的架构:

也就是说,责任链模式满足了请求发送者与请求处理者之间的松耦合,抽象非核心的部分,以链式调用的方式对请求对象进行处理

这么说不明白?那么下面通过实际例子让你明白。

 

不使用责任链模式

为什么要使用责任链模式,那么我们得知道不使用责任链模式有什么坏处,然后通过使用责任链模式如何将代码优化。

现在有一个场景:小明要去上学,妈妈给小明列了一些上学前需要做的清单(洗头、吃早饭、洗脸),小明必须按照妈妈的要求,把清单上打钩的事情做完了才可以上学。

首先我们定义一个准备列表PreparationList:

 1 public class PreparationList {
2
3 /**
4 * 是否洗脸
5 */
6 private boolean washFace;
7
8 /**
9 * 是否洗头
10 */
11 private boolean washHair;
12
13 /**
14 * 是否吃早餐
15 */
16 private boolean haveBreakfast;
17
18 public boolean isWashFace() {
19 return washFace;
20 }
21
22 public void setWashFace(boolean washFace) {
23 this.washFace = washFace;
24 }
25
26 public boolean isWashHair() {
27 return washHair;
28 }
29
30 public void setWashHair(boolean washHair) {
31 this.washHair = washHair;
32 }
33
34 public boolean isHaveBreakfast() {
35 return haveBreakfast;
36 }
37
38 public void setHaveBreakfast(boolean haveBreakfast) {
39 this.haveBreakfast = haveBreakfast;
40 }
41
42 @Override
43 public String toString() {
44 return "ThingList [washFace=" + washFace + ", washHair=" + washHair + ", haveBreakfast=" + haveBreakfast + "]";
45 }
46
47 }

定义了三件事情:洗头、洗脸、吃早餐。

接着定义一个学习类,按妈妈要求,把妈妈要求的事情做完了再去上学:

 1 public class Study {
2
3 public void study(PreparationList preparationList) {
4 if (preparationList.isWashHair()) {
5 System.out.println("洗脸");
6 }
7 if (preparationList.isWashHair()) {
8 System.out.println("洗头");
9 }
10 if (preparationList.isHaveBreakfast()) {
11 System.out.println("吃早餐");
12 }
13
14 System.out.println("我可以去上学了!");
15 }
16
17 }

这个例子实现了我们的需求,但是不够优雅,我们的主流程是学习,但是把要准备做的事情这些动作耦合在学习中,这样有两个问题:

  • PreparationList中增加一件事情的时候,比如增加化妆、打扫房间,必须修改study方法进行适配
  • 当这些事情的顺序需要发生变化的时候,必须修改study方法,比如先洗头再洗脸,那么7~9行的代码必须和4~6行的代码互换位置

最糟糕的写法,只是为了满足功能罢了,违背开闭原则,即当我们扩展功能的时候需要去修改主流程,无法做到对修改关闭、对扩展开放。

 

使用责任链模式

接着看一下使用责任链模式的写法,既然责任链模式的特点是“链上的每个对象都持有下一个对象的引用”,那么我们就这么做。

先抽象出一个AbstractPrepareFilter:

 1 public abstract class AbstractPrepareFilter {
2
3 private AbstractPrepareFilter nextPrepareFilter;
4
5 public AbstractPrepareFilter(AbstractPrepareFilter nextPrepareFilter) {
6 this.nextPrepareFilter = nextPrepareFilter;
7 }
8
9 public void doFilter(PreparationList preparationList, Study study) {
10 prepare(preparationList);
11
12 if (nextPrepareFilter == null) {
13 study.study();
14 } else {
15 nextPrepareFilter.doFilter(preparationList, study);
16 }
17 }
18
19 public abstract void prepare(PreparationList preparationList);
20
21 }

留一个抽象方法prepare给子类去实现,在抽象类中持有下一个对象的引用nextPrepareFilter,如果有,则执行;如果没有表示链上所有对象都执行完毕,执行Study类的study()方法:

 1 public class Study {
2
3 public void study() {
4 System.out.println("学习");
5 }
6
7 }

接着我们实现AbstractPrepareList,就比较简单了,首先是洗头:

 1 public class WashFaceFilter extends AbstractPrepareFilter {
2
3 public WashFaceFilter(AbstractPrepareFilter nextPrepareFilter) {
4 super(nextPrepareFilter);
5 }
6
7 @Override
8 public void prepare(PreparationList preparationList) {
9 if (preparationList.isWashFace()) {
10 System.out.println("洗脸");
11 }
12
13 }
14
15 }

接着洗脸:

 1 public class WashHairFilter extends AbstractPrepareFilter {
2
3 public WashHairFilter(AbstractPrepareFilter nextPrepareFilter) {
4 super(nextPrepareFilter);
5 }
6
7 @Override
8 public void prepare(PreparationList preparationList) {
9 if (preparationList.isWashHair()) {
10 System.out.println("洗头");
11 }
12
13 }
14
15 }

最后吃早餐:

 1 public class HaveBreakfastFilter extends AbstractPrepareFilter {
2
3 public HaveBreakfastFilter(AbstractPrepareFilter nextPrepareFilter) {
4 super(nextPrepareFilter);
5 }
6
7 @Override
8 public void prepare(PreparationList preparationList) {
9 if (preparationList.isHaveBreakfast()) {
10 System.out.println("吃早餐");
11 }
12
13 }
14
15 }

最后我们看一下调用方如何编写:

 1 @Test
2 public void testResponsibility() {
3 PreparationList preparationList = new PreparationList();
4 preparationList.setWashFace(true);
5 preparationList.setWashHair(false);
6 preparationList.setHaveBreakfast(true);
7
8 Study study = new Study();
9
10 AbstractPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter(null);
11 AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter);
12 AbstractPrepareFilter washHairFilter = new WashHairFilter(washFaceFilter);
13
14 washHairFilter.doFilter(preparationList, study);
15 }

至此使用责任链模式修改这段逻辑完成,看到我们完成了学习与准备工作之间的解耦,即核心的事情我们是要学习,此时无论加多少准备工作,都不需要修改study方法,只需要修改调用方即可。

但是这种写法好吗?个人认为这种写法虽然符合开闭原则,但是两个明显的缺点对客户端并不友好:

  • 增加、减少责任链对象,需要修改客户端代码,即比如我这边想要增加一个打扫屋子的操作,那么testResponsibility()方法需要改动
  • AbstractPrepareFilter washFaceFilter = new WashFaceFilter(haveBreakfastFilter)这种调用方式不够优雅,客户端需要思考一下,到底真正调用的时候调用三个Filter中的哪个Filter

为此,我们来个终极版的、升级版的责任链模式。

 

升级版责任链模式

上面我们写了一个责任链模式,这种是一种初级的符合责任链模式的写法,最后也写了,这种写法是有明显的缺点的,那么接着我们看一下升级版的责任链模式如何写,解决上述问题。

以下的写法也是Servlet的实现方式,首先还是抽象一个Filter:

 1 public interface StudyPrepareFilter {
2
3 public void doFilter(PreparationList preparationList, FilterChain filterChain);
4
5 }

注意这里多了一个FilterChain,也就是责任链,是用于串起所有的责任对象的,它也是StudyPrepareFilter的一个子类:

 1 public class FilterChain implements StudyPrepareFilter {
2
3 private int pos = 0;
4
5 private Study study;
6
7 private List<StudyPrepareFilter> studyPrepareFilterList;
8
9 public FilterChain(Study study) {
10 this.study = study;
11 }
12
13 public void addFilter(StudyPrepareFilter studyPrepareFilter) {
14 if (studyPrepareFilterList == null) {
15 studyPrepareFilterList = new ArrayList<StudyPrepareFilter>();
16 }
17
18 studyPrepareFilterList.add(studyPrepareFilter);
19 }
20
21 @Override
22 public void doFilter(PreparationList thingList, FilterChain filterChain) {
23 // 所有过滤器执行完毕
24 if (pos == studyPrepareFilterList.size()) {
25 study.study();
26 }
27
28 studyPrepareFilterList.get(pos++).doFilter(thingList, filterChain);
29 }
30
31 }

即这里有一个计数器,假设所有的StudyPrepareFilter没有调用完毕,那么调用下一个,否则执行Study的study()方法。

接着就比较简单了,实现StudyPrepareFilter类即可,首先还是洗头:

 1 public class WashHairFilter implements StudyPrepareFilter {
2
3 @Override
4 public void doFilter(PreparationList preparationList, FilterChain filterChain) {
5 if (preparationList.isWashHair()) {
6 System.out.println("洗完头发");
7 }
8
9 filterChain.doFilter(preparationList, filterChain);
10 }
11
12 }

注意,这里每个实现类需要显式地调用filterChain的doFilter方法。洗脸:

 1 public class WashFaceFilter implements StudyPrepareFilter {
2
3 @Override
4 public void doFilter(PreparationList preparationList, FilterChain filterChain) {
5 if (preparationList.isWashFace()) {
6 System.out.println("洗完脸");
7 }
8
9 filterChain.doFilter(preparationList, filterChain);
10 }
11
12 }

吃早饭:

 1 public class HaveBreakfastFilter implements StudyPrepareFilter {
2
3 @Override
4 public void doFilter(PreparationList preparationList, FilterChain filterChain) {
5 if (preparationList.isHaveBreakfast()) {
6 System.out.println("吃完早饭");
7 }
8
9 filterChain.doFilter(preparationList, filterChain);
10 }
11
12 }

最后看一下调用方:

 1 @Test
2 public void testResponsibilityAdvance() {
3 PreparationList preparationList = new PreparationList();
4 preparationList.setWashFace(true);
5 preparationList.setWashHair(false);
6 preparationList.setHaveBreakfast(true);
7
8 Study study = new Study();
9
10 StudyPrepareFilter washFaceFilter = new WashFaceFilter();
11 StudyPrepareFilter washHairFilter = new WashHairFilter();
12 StudyPrepareFilter haveBreakfastFilter = new HaveBreakfastFilter();
13
14 FilterChain filterChain = new FilterChain(study);
15 filterChain.addFilter(washFaceFilter);
16 filterChain.addFilter(washHairFilter);
17 filterChain.addFilter(haveBreakfastFilter);
18
19 filterChain.doFilter(preparationList, filterChain);
20 }

完美解决第一版责任链模式存在的问题,至此增加、修改责任对象客户端调用代码都不需要再改动。

有的人可能会问,你这个增加、减少责任对象,testResponsibilityAdvance()方法,不是还得addFilter,或者删除一行吗?我们回想一下,Servlet我们增加或减少Filter需要改动什么代码吗?不用,我们需要改动的只是web.xml而已。同样的道理,FilterChain里面有studyPrepareFilterList,我们完全可以把FilterChain做成一个Spring Bean,所有的Filter具体实现类也都是Spring Bean,注入studyPrepareFilterList就好了,伪代码为:

1 <bean id="filterChain" class="xxx.xxx.xxx.FilterChain">
2 <property name="studyPrepareFilterList">
3 <list>
4 <ref bean="washFaceFilter" />
5 <ref bean="washHairFilter" />
6 <ref bean="haveBreakfastFilter" />
7 </list>
8 </property>
9 </bean>

这样是不是完美解决了问题?我们新增、减少Filter,或者修改Filter顺序,只需要修改.xml文件即可,不仅核心逻辑符合开闭原则,调用方也符合开闭原则。

 

责任链模式的使用场景

这个就不多说了,最典型的就是Servlet中的Filter,有了上面的分析,大家应该也可以理解Servlet中责任链模式的工作原理了,然后为什么一个一个的Filter需要配置在web.xml中。

 

责任链模式的结构

想想看,好像责任链模式也没有什么太复杂的结构,将责任抽象,实现责任接口,客户端发起调用,网上找了一张图表示一下:

 

 

责任链模式的优点及使用场景

最后说说责任链模式的优点吧,大致有以下几点:

  • 实现了请求发送者与请求处理者之间的松耦合
  • 可动态添加责任对象、删除责任对象、改变责任对象顺序,非常灵活
  • 每个责任对象专注于做自己的事情,职责明确

什么时候需要用责任链模式?这个问题我是这么想的:系统设计的时候,注意区分主次就好,即哪部分是核心流程,哪部分是辅助流程,辅助流程是否有N多if...if...if...的场景,如果是且每个if都有一个统一的抽象,那么抽象辅助流程,把每个if作为一个责任对象进行链式调用,优雅实现,易复用可扩展。

版权声明
本文为[hiram-QI]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/hiramxq/p/13948958.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云