前言

对于已经工作了的小伙伴,你应该是见过"责任链"这种面向对象的设计模式的,还在上学的小伙伴也不用着急,你迟早会接触到的。本文旨在让小白同学和不太熟悉责任链的朋友能够迅速对这一设计模式有一个大致的了解。

在我们的工农业生产中,经常有这样的场景:一个任务、事务、流程等都需要很多不同的步骤,来完成不同的计算或者收集不同的数据。

为了维护一个比较复杂,有时甚至是对顺序敏感的任务流程,我们经常在代码的编写和设计上采用"责任链"设计模式。

究竟什么是"责任链"呢?咱们看下面这个例子。

例子

假设你也"穿越"到了清朝,是会写代码的和珅和中堂,皇上马上要南巡。请你用代码封装并模拟"乾隆下江南"这件事。

你要怎么安排万岁爷的行程?要知道这可是个大工程,中间可不能有差错,一旦出了什么岔子可是要掉脑袋的 ????

但皇上又是性情中人,行程可能经常更改,甚至半路就微服私访。

所以我们在伺候皇上下江南的时候,既得让皇上的行程有序进行,又要尽量适应圣上由于一时兴起而可能做出的变化。

怎么设计呢?如果把皇上的行程都写在一起执行,有两个不好的地方:

  1. 行程太多,而且全都事关重大,这么远的路,全都要你一个人打理,哪里一不注意出了乱子,脑袋就要搬家;

  2. 行程多,所以增改起来太麻烦,一旦有改动圣上的行程表容易乱。毕竟行程写在一起,好似 一堆乱麻,条理不清。

所以问题来啦,和大人您可怎么排圣上的行程呢?

和大人莫急,看看地图我们就知道,乾隆从北京到杭州要顺序经过直隶、山东、江苏、浙江四省(基本就是现在京沪高铁的路子):

这样和大人就可以按省把任务大致划分为四个部分,责成四省的官员们分担这一个大工程,把他们应尽的的责任连成一个有序的链条,然后依次让他们执行伺候皇上的任务。

这样一来解决了行程过于丰富,和大人一个人安排不过来的问题,二来保证了各个步骤的灵活安排(后面的例子讲),三来哪一步出了问题还便于问责(甩锅,否则全是自己的错)。

好了,说了这么多,现在切入技术层面。

设计

Step1:

首先总结一下我们所研究的问题中的名词,来确定大概需要哪些类:

  1. 皇帝(乾隆)

  2. 行程的管理者(和中堂)

  3. 各省官员(具体干活的公仆们)

Step2:

再来确定各个类之间的关系:

  • 最容易看出来的是各省官员是同僚关系,他们都要接待乾隆,只是在皇上南巡的过程中出场顺序和做的具体接待行为不一样,比如:

    • 直隶总督会带乾隆去避暑山庄,

    • 山东巡抚会张罗着皇上祭拜孔庙,

    • 苏州织造让皇上游览园林,

    • 而杭州知州就带着皇上去西湖苏堤。

  • 这里告诉大家 OOD 中一个优化设计的小口诀:变化的抽接口,相同的建模版

所以我们在这里面对官员们不同的行为,最好把他们抽象成接口或者抽象类,这里我们采用官员(Official) 这个抽象类。

而和大人作为总管,他既要掌握皇帝的动向,又要辖制各省官员,所以在类的层面上和大人(PrimeMinister)这个类就得有指向皇帝(Emperor)和官员列表的引用。

下面上 UML 图。

UML 图

各省同僚:

而你和大人,作为乾隆面前的红人,得统筹安排皇帝的行程,既要挟持皇帝,又要掌管各省官员,让他们有序地执行任务:

责任链一般都至少有一个被处理的对象,作为参数传入各个步骤,这里的乾隆就是这个被处理(伺候)的对象。

代码

作为官员这个抽象类,我们考虑到实际情况,他要安排一个地方并陪同皇帝参观、游览,其实就是一句话:伺候皇上。

所以他有一个抽象方法 serve,接受皇帝(Emperor)这个对象

@Data
public abstract class Official {protected String title;protected abstract void serve(Emperor emperor);@Overridepublic String toString() {return title;}
}

这里为了区别不同的官员,我们还给了官员(Official)类一个成员变量 title。

Official 下面有具体实现的类,代表各省官员,他们自己有自己具体的方式去服务吾皇,比如直隶总督,他是这么干的:

public class HebeiOfficial extends Official {public HebeiOfficial() {this.title = "直隶总督";}@Overrideprotected void serve(Emperor emperor) {emperor.play(this, "避暑山庄");}
}

这里在 serve 里面完全让参数"皇帝"自己决定怎么玩,(顺便说句题外话,这种让参数这个"外来的和尚"念经的方式,在各种设计模式里很常见。如果把这里的 Emperor 换成 Comparator,相信很多小伙伴就感觉有点像策略模式了。而且"直隶总督"也可以在皇帝 play 之前或者之后分别做一些事情,这像不像用 JDK 的代理的时候中那个 InvocationHandler 对待 Method 的方式?或者 Spring 中对于 Aspect 的处理?另外在 Visitor 等设计模式中你也能看到这种写法的身影)

其他官员的写法类似,只是换个地方供皇帝游览而已,参见后面的输出结果,这里略。

而作为皇帝,乾隆只管着玩就好,当然了,你和中堂可以安排当地的官员陪同,所以 皇帝类只有一个 play 方法,这里用一个字符串简单表示去游览的地方。

为了防止乾隆南下期间有人在北京"另立新君"(执行 new Emperor()),这个"皇帝"对象的创建过程采用了单例模式,保证整个 JVM 里面就只有这么一个皇上,而且名字叫"乾隆":

public class Emperor {private static final Emperor INSTANCE = new Emperor("乾隆");private final String name;private Emperor(String name) {this.name = name;}public static Emperor getInstance() {return INSTANCE;}public void play(Official official, String place){System.out.println(official.getTitle() + " 安排 " + name + "皇帝游览了: " + place);}
}

而你,和珅和大人,只需要按各省顺序,合理安排好下面的官员,然后请出皇上并昭告天下:圣上下江南了,沿途各省小心伺候就好:

public class PrimeMinister {private static List<Official> list = new ArrayList<>();public static void main(String[] args) {// 下令沿途各省官员准备好list.add(new HebeiOfficial());list.add(new ShandongOfficial());list.add(new JiangsuOfficial());list.add(new ZhejiangOfficial());// 请出皇上Emperor emperor = Emperor.getInstance();// 昭告天下:万岁爷起驾下江南!沿途各省依次伺候圣上System.out.println("乾隆下江南!");start(list, emperor);}private static void start(List<Official> officials, Emperor emperor) {for (Official o : officials) {o.serve(emperor);}}
}

看看,你的任务是不是简明多了,只需要维护好这个沿途各省官员的花名册即可。

更重要的是,你不用亲自负责了,下面的人谁办事不力,就要谁的脑袋!

只要自己的这个"花名册"或者"行程表"没写错,咱的脑袋就算保住啦。

而且各个官员的任务也比较单一,他们自己也更不容易出错。下面是整个行程模拟的执行情况:

乾隆下江南!
直隶总督 安排 乾隆皇帝游览了: 避暑山庄
山东巡抚 安排 乾隆皇帝游览了: 曲阜孔庙
苏州织造 安排 乾隆皇帝游览了: 苏州园林
杭州知州 安排 乾隆皇帝游览了: 西湖苏堤

嗯,一切看上去似乎还不错,各省官员按照顺序,依次完成了任务,把万岁爷伺候的还不错,没有什么异常状况发生,总算松了口气。

但是,现在来了个突发情况:皇上突然要求,在路过山东的时候加一个环节——大明湖畔三日游!

为啥要特意去那里?咱也不敢问呐!只管准备就好。

幸好我们的行程又已经有了大致框架,赶紧查,大明湖那里归谁管,哦,济南知府,就是他了!

现在只需把他也加到"花名册":责令济南知府安排皇上在大明湖畔三天的行程,不得有误,否则拿你试问!下面是和大人这边要做的改动:

    ...以上略...list.add(new HeibeiOfficial());// 加入济南知府,让他干活,他知道在大明湖畔该怎么玩list.add(new JinanOfficial());list.add(new ShandongOfficial());list.add(new JiangsuOfficial());list.add(new ZhejiangOfficial());...以下略...

而另一边济南知府这里,他也是属于官僚体制了(Official 的子类),所以也要极尽所能,让圣上在大明湖畔玩得开心:

public class JinanOfficial extends Official{public JinanOfficial() {title = "济南知府";}@Overrideprotected void serve(Emperor emperor) {emperor.play(this, "大明湖畔");}
}

再次执行程序,模拟圣上的行程,结果输出如下:

乾隆下江南!
直隶总督 安排 乾隆皇帝游览了: 避暑山庄
济南知府 安排 乾隆皇帝游览了: 大明湖畔
山东巡抚 安排 乾隆皇帝游览了: 曲阜孔庙
苏州织造 安排 乾隆皇帝游览了: 苏州园林
杭州知州 安排 乾隆皇帝游览了: 西湖苏堤

嗯,这下总算又迎合了圣意,以后皇上再来什么其他的行程也不怕了(只要他不微服私访,微服私访您找纪晓岚去啊,单一责任原则,专门的类干专门的事儿不是?)。

只要找到当地具体的官员,一纸命令:你给我极尽所能招待皇上,具体怎么招待,你看着办,伺候不好万岁爷,我要你脑袋!

当然了,皇帝也可能临时删掉南巡中的某个环节,我们直接把它从行程列表中删除就好,而且什么时候想再重新加进来还可以随时添加,做到了可以"灵活插拔",把代码的改动减到了最小,有新的业务逻辑加进来的时候,只是做添加,这样既不容易出错,也确保了代码的弹性扩展,而且当前责任链中的步骤,如果没有状态相关的信息的话,也可以被组装到其他的责任链中。

如果是我们的真实项目,我们甚至可以把工作步骤的列表配置在 Spring Boot 的配置文件里,开启流程的这个类,只要读取配置,然后把各个步骤依次执行。

这样如果有修改只要改动配置文件即可,在 Java 代码里无需任何改动。

总结与拓展

以上其实只是一个责任链模式最简单的应用,它是一个有序列表里面装了各个任务的步骤,然后依次运行到最后。

我们可以把它写在自己的程序里,也可以把它抽象出来做成产品,让其他人自由扩展与配置,尽量减少重复制造轮子。

有很多工作流引擎便是这样,比如 ActivitiNetflixConductor 等。不光这些,就连你 最常用的 SpringMVC 甚至是 Tomcat 都用到了责任链模式,只不过他们的责任链是双向的,分别处理请求和响应,而且他们的处理顺序是刚好相反的,本质上是用类似递归的方法正序倒序各便历了一次(Filter 或 Interceptor 的)数组。

另外在一些持续集成和持续部署的框架中,如 Jenkins,会有管道(Pipeline)的概念,当你在做出 git push 提交代码之后,会触发整个流程开始一步步地运作:拉取代码(Checkout code)、构建(Build)、测试(Test)等,直到部署(Deploy)完成并运行脚本关闭旧版本的服务并启动最新部署的服务。这个"流水线"(Pipeline)其实也是一个可以让你用代码脚本来配置的责任链。

没有责任链模式的应用,你甚至都无法运行任何一个 Java 程序。因为类加载一般遵循"双亲委派"机制,实际上是用类似递归的方法正序和倒序各便历了一次 Classloader 类所构成的链表(题外话,想把一个链表翻转过来,可以参见齐姐之前写过的:),只不过其中的逻辑比较复杂,而且还应用了"模板方法"这一设计模式。由于本文只是做一个责任链模式的简单入门,这些不做过多展开了。

综上,充分理解和应用责任链设计模式,对我们的日常工作和阅读源码都很有帮助,能让我们有效提高代码的扩展性和可读性,希望对你也有所帮助。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

一文带你玩转设计模式之「责任链」相关推荐

  1. 变现利器!一文带你学会应用内添加「贴片广告」

    什么是贴片广告 贴片广告是一种在视频播放过程中插入的视频或图片广告.视频广告一般是15s, 可以点跳过按钮直接进入视频播放界面,也可以等广告展示结束自动进入视频播放界面. 贴片广告的优势 相较于其他形 ...

  2. 简易理解设计模式之:责任链模式——OA中请假流程示例

    介绍: 责任链模式属于行为型设计模式.它的定义为:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,只到有对象处理它为止. 类图: ...

  3. 设计模式学习笔记——责任链(Chain of Responsibility)模式

    设计模式学习笔记--责任链(Chain of Responsibility)模式 @(设计模式)[设计模式, 责任链模式, chain of responsibility] 设计模式学习笔记责任链Ch ...

  4. 一文带你玩转 RustChinaConf 2023,内含赞助商展位活动福利和 Workshop 介绍

    除了两天干货满满的会议外,RustChinaConf 的赞助商也准备了精美的周边礼物等待大家去打卡.每位参会者在签到的时候会获得一张集章卡,集齐上面所有的章,可至签到处兑换精美礼物一份.偷偷剧透一下, ...

  5. 设计模式示例_责任链设计模式示例

    设计模式示例 本文是我们名为" Java设计模式 "的学院课程的一部分. 在本课程中,您将深入研究大量的设计模式,并了解如何在Java中实现和利用它们. 您将了解模式如此重要的原因 ...

  6. LOL设计模式之「策略模式」

    之前写过一篇什么是「设计模式」?,没有类图,没有代码,有些同学说看不太懂,今天给大家带来策略模式(有图,有码,有真相!). 英雄联盟(LOL) 玩过LOL的同学都知道,LOL有上百个英雄,如果用OO技 ...

  7. LOL设计模式之「策略模式」 1

    之前写过一篇什么是「设计模式」?,没有类图,没有代码,有些同学说看不太懂,今天给大家带来策略模式(有图,有码,有真相!). 英雄联盟(LOL) 玩过LOL的同学都知道,LOL有上百个英雄,如果用OO技 ...

  8. 【文末送5本书】与「韦神」齐名,35岁刁晗生任教清华!18岁一战成名,数学界颜值巅峰...

      视学算法报道   编辑:桃子 文末包邮送5本价值百元的高质量技术书籍 [新智元导读]除「韦神」外,北大还有一位和韦东奕并驾齐驱的数学天才.他就是刁晗生,18岁在第46届IMO上一战成名,还一手集齐 ...

  9. 《java设计模式》之责任链模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述责任链(Chain of Responsibility)模式的: 责任链模式是一种对象的行为模式.在责任链模式里,很多对象由每一个对象对其 ...

最新文章

  1. 浅谈Generator和Promise原理及实现
  2. Altium Designer -- PCB 叠层设计
  3. 只显示小方格_木托盘拼成院子围栏,镂空方格里都种上菜,一物两用,收获满满...
  4. JavaScriptjQuery.返回多个值的函数
  5. Ubuntu系统安装(win7双系统)
  6. mysql增量备份二进制日志,mysql增量备份二进制日志shell脚本
  7. 浏览器内存不足导致页面崩溃_深度精读:浏览器渲染原理 [8000字图文并茂]
  8. void类型和void *的用法
  9. hdoj 1013 Digital Roots
  10. 高速建成Android开发环境ADT-Bundle和Hello World
  11. 使用sun misc Unsafe及反射对内存进行内省 introspection
  12. C# 二进制数据、图片存入、读出 sql server 数据库
  13. java服务器 protobuf_服务器端用PHP,客户端用JAVA,通过protobuf互相通信[原创] | 学步园...
  14. VMware 12 许可密钥
  15. JavaScript 获取当前URL信息
  16. 开启阿里云linux下的pure-ftpd被动模式,解决flashfxp可连接但无法下载的问题
  17. 利用爬虫来制作一个翻译小软件
  18. android rgb接口,Android RGB颜色查询对照表
  19. Mybatis源码学习-MapperMethod
  20. 程序设计思维与实践 Week15 作业 A-ZJM与霍格沃兹

热门文章

  1. Swagger3.0新版带来的新变化
  2. 51单片机程序及调试步骤实战经验
  3. java布局工具,Java Swing:库,工具,布局管理器
  4. C语言----求解N以内的素数的两种典型方法以及其优化
  5. 网校mysql设计规范_点击!2019年9月二级MySQL试题
  6. P4619 [SDOI2018]旧试题(莫比乌斯反演,建图优化三重枚举,三元环计数,神仙好题,超级清晰易懂)
  7. 【题解】P1080 国王游戏(贪心+高精python天下第一)
  8. 机械硬盘旋转时间_详解硬盘转速5400转和7200转的区别
  9. 设计模式——命令模式(Command Pattern)
  10. 人工智能的影响调查_调查报告|文科大学生群体对于人工智能影响 就业的认知程度:基于访谈的质性研究...