设计模式——2_0 职责链(Chain of Responsibility)

楼下一个男人并得要死,那家隔壁的一家唱着留声机,对面是弄孩子。楼上有两人狂笑;还有打牌声,河中的船上有女人哭她死去的母亲。人类的悲欢并不相通,我只觉得他们吵闹

——鲁迅



文章目录

  • 定义
  • 图纸
  • 一个例子:如何把苹果放到合适的篮子里
    • 苹果分配器
    • 不同的标准
      • 组合对象
      • 职责链
  • 碎碎念
    • 职责链和事件响应
    • 职责链和组合
    • 清晰的结构和复杂的代码

定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理她为止

你玩过那种过关式的动作游戏吗?当你和小伙伴进入一个副本之后,必须按照副本的地图通过各个房间,最终干掉boss完成副本。这时候如果把玩家看作 请求,则副本就是一个职责链。所有的房间都有机会干掉玩家, 而玩家也有可能通过所有房间完成副本;而各个房间之间是不存在什么关联的,你可以把他们拆开另外组合



图纸

在这里插入图片描述



一个例子:如何把苹果放到合适的篮子里

无论是工厂、批发商还是其他的产品产出方,在自己的产品从成品到正式出货之前都要经过一系列的流程。哪怕是农民伯伯卖白菜,也会把烂叶子摘掉后再往外卖。现在如果我们要用代码来展示出货前的流程,我们该怎么做呢?

准备好了吗?这次的例子开始了


假定 生产苹果的大型基地A 需要根据采摘到的苹果的外观把苹果分成 X/Y/Z 三种类型的苹果(当然每种类型苹果价格不同) 。现在 A 为了推行全自动的出货模块,购入了一台 苹果外观打分器,这台机器可以根据输入的苹果的外观输出一个 外观分数,你的任务是根据这个 外观分数 和 你拿到的苹果,决定他应该被放到哪个篮子里,整个流程就像这样:

在这里插入图片描述


苹果分配器

为了完成这个任务,我们会创建一个 苹果分配器 让这个苹果分配器根据苹果的外观分数把他们放到不同的篮子里去,就像这样:

在这里插入图片描述

/**苹果bean
**/
public class Apple {/*** 根据苹果外貌评分器写入的外貌分数*/private final float appearance;public Apple(float appearance) {this.appearance = appearance;}public float getAppearance() {return appearance;}
}/*** 苹果分配器*/
public class AppleDispatcher {/*** 根据苹果的外貌分数把他们放入不同的篮子里*/public void dispatch(Apple apple) {float appearance = apple.getAppearance();if (appearance >= 75) {//放入X类型的盒子中} else if (appearance >= 45) {//放入Y类型的盒子里} else if (appearance > 0) {//放入Z类型的盒子里} else {//外貌分<=0,证明出现了异常throw new RuntimeException("输入的苹果的外貌分不合法,appearance=" + appearance);}}
}

我们创建了一个 AppleDispatcher 作为苹果分配器。其中有一个 dispatch 方法,接收一个苹果对象作为参数,然后根据里面的外貌分把他们分到不同的篮子里


不同的标准

上文的 AppleDispatcher 很好的完成了他的使命,但是系统运行一段时间后公司收到了一堆投诉,说是Z类型的盒子里出现了 被虫蛀的痕迹和摔烂的情况。经过QA部1同事夜以继日的加班,我们发现问题是出在外观分上:

我们的 苹果外观打分器 并不能识别出虫洞和掉到地上摔坏的痕迹,所以就算被检测的苹果被虫蛀了、烂了,也只是评分低一点,并不会不给他外貌分


于是我们决定修改现有的出货模块,新增以下规则:

  1. 当苹果的外观分<=45时,需要对其进行 外观检查,以判断是否被虫蛀
  2. 当苹果经过 外观检查 后发现没有被虫蛀,而是摔坏了,则输送到牧场喂猪

也就是说,我们的流程要变成这样:

在这里插入图片描述

修改后的流程中新增了两个全新的分支,每一个苹果在进入 AppleDispatcher 的时候都有可能被分配到任何一个分支中,要怎么实现这样的效果呢?


组合对象

显而易见的一种方法,是直接在 AppleDispatcher 中添加逻辑,把所有的可能性都写出来。更进一步,你甚至可以定义一个验证器,用于抽象这个检查的过程,就像这样:

在这里插入图片描述

public class Apple {/*** 根据苹果外貌评分器写入的外貌分数*/private final float appearance;/*** 是否有虫*/private boolean haveWorm;/*** 是否摔伤*/private boolean isFall;public Apple(float appearance) {this.appearance = appearance;}public float getAppearance() {return appearance;}public boolean isHaveWorm() {return haveWorm;}public void setHaveWorm(boolean haveWorm) {this.haveWorm = haveWorm;}public boolean isFall() {return isFall;}public void setFall(boolean fall) {isFall = fall;}
}public interface Verifier {/*** 验证* @param apple 被验证的苹果* @return true:通过验证*/boolean verify(Apple apple);
}public class WormVerifier implements Verifier{@Overridepublic boolean verify(Apple apple) {return !apple.isHaveWorm();//返还这个苹果是否有虫}
}public class AppearanceVerifier implements Verifier{@Overridepublic boolean verify(Apple apple) {return !apple.isFall();//返还这个苹果是否摔伤}
}/*** 苹果分配器*/
public class AppleDispatcher {/*** 根据苹果的外貌分数把他们放入不同的篮子里*/public void dispatch(Apple apple) {float appearance = apple.getAppearance();if (appearance >= 75) {//放入X类型的盒子中} else if (appearance >= 45) {//放入Y类型的盒子里} else if (appearance > 0) {if(new WormVerifier().verify(apple)){//没有虫子if(new AppearanceVerifier().verify(apple)){//没有摔伤//放入Z类型的盒子里}else {//喂猪}}else {//直接报废}} else {//外貌分<0,证明出现了异常throw new RuntimeException("输入的苹果的外貌分不合法,appearance=" + appearance);}}
}

在这个实现里,我们通过建立 Verifier(验证器) 类簇把不同的验证方式封装到不同的类中,然后在 AppleDispatcher 中通过这些验证器的结果把苹果送到不同的结果分支中去,而且将来如果出现新的验证方式,我不需要修改已有的验证器,只需要新增子项。


但是这依然是很糟糕的设计

你可能发现了,我并没有组合 AppleDispatcherVerifier,而是在 dispatch 方法中做紧耦合。这并不是我不想组合他们,而是我没办法组合他们,就算 Verifier 被解耦出来了,但是 AppleDispatcher 依然需要根据参数苹果在哪个验证器中被识别成异常,来决定苹果的走向

也就是说,AppleDispatcher 依然需要知道整个外观检查流程中的全部信息,只要我修改流程,还是要修改已经封装好的 dispatch 方法,这是我不愿意看到的


我希望有这样一个类的对象,可以在检测的同时直接对苹果做一个流向判断,而且不需要了解上下文此时的状态。而我在外部会给他提供一个调用环境,来调度当前的请求

当然有这样的写法,答案就是本文的主题,职责链


职责链

如果用职责链实现这个需求的话,是这样的:

在这里插入图片描述

AppleCheckExecutor

/*** 苹果检查执行器*/
public abstract class AppleCheckExecutor {protected AppleHandler root;//根操作者public AppleCheckExecutor() {initHandleChain();}public void addAppleHandler(AppleHandler handler) {if (root == null) {root = handler;} else {addAppleHandler(handler, root);}}private void addAppleHandler(AppleHandler handler, AppleHandler lastHandler) {AppleHandler nextHandler = lastHandler.getNextHandler();if (nextHandler == null) {//没有下级HandlerlastHandler.setNextHandler(handler);} else {addAppleHandler(handler, nextHandler);}}public void executor(Apple apple) {if (root == null) {//如果第一个处理器都没有写入,则直接执行默认处理方式defaultHandle(apple);} else {Apple result = root.handle(apple);if (result != null) {//没有任何处理器相应defaultHandle(apple);}}}/*** 初始化处理器链*/protected abstract void initHandleChain();/*** 默认处理方式*/protected abstract void defaultHandle(Apple apple);
}/*** 高分执行器*/
public class HeightExecutor extends AppleCheckExecutor{@Overrideprotected void initHandleChain() {//什么都不写入}@Overrideprotected void defaultHandle(Apple apple) {//放入X类型的盒子中}
}/*** 中等分数执行器*/
public class MinExecutor extends AppleCheckExecutor{@Overrideprotected void initHandleChain() {//什么都不写入}@Overrideprotected void defaultHandle(Apple apple) {//放入Y类型的盒子里}
}/*** 低分执行器*/
public class LowExecutor extends AppleCheckExecutor{@Overrideprotected void initHandleChain() {//写入外观检查流程addAppleHandler(new WormHandler());addAppleHandler(new AppearanceHandler());}@Overrideprotected void defaultHandle(Apple apple) {//放入Z类型盒子中}
}

AppleHandler

/*** 苹果验证控制器*/
public abstract class AppleHandler {/*** 下一级处理器*/protected AppleHandler nextHandler;public AppleHandler() {}public AppleHandler getNextHandler() {return nextHandler;}public void setNextHandler(AppleHandler nextHandler) {this.nextHandler = nextHandler;}/*** 验证是否通过这个控制器*/protected abstract boolean verify(Apple apple);/*** 未通过验证时需要执行的操作*/protected abstract void cannot(Apple apple);/*** 处理苹果*/public Apple handle(Apple apple) {if (verify(apple)) {//通过验证,把请求发送到下一级控制器中if (nextHandler == null) {//请求结束return apple;} else {return nextHandler.handle(apple);}} else {//没有通过验证cannot(apple);return null;}}
}/*** 虫子处理器*/
public class WormHandler extends AppleHandler {public WormHandler() {}@Overrideprotected void cannot(Apple apple) {//报废}@Overrideprotected boolean verify(Apple apple) {return !apple.isHaveWorm();//返还这个苹果是否有虫}
}/*** 外观处理器*/
public class AppearanceHandler extends AppleHandler {public AppearanceHandler() {}@Overrideprotected boolean verify(Apple apple) {return !apple.isFall();}@Overrideprotected void cannot(Apple apple) {//喂猪}
}

dispatcher

/*** 苹果分配器*/
public class AppleDispatcher {private final AppleCheckExecutor heightExecutor = new HeightExecutor();//高分执行器private final AppleCheckExecutor minExecutor = new MinExecutor();//中分执行器private final AppleCheckExecutor lowExecutor = new LowExecutor();//低分执行器/*** 根据苹果的外貌分数把他们放入不同的篮子里*/public void dispatch(Apple apple) {float appearance = apple.getAppearance();if (appearance >= 75) {heightExecutor.executor(apple);} else if (appearance >= 45) {minExecutor.executor(apple);} else if (appearance > 0) {lowExecutor.executor(apple);} else {//外貌分<0,证明出现了异常throw new RuntimeException("输入的苹果的外貌分不合法,appearance=" + appearance);}}
}

在职责链的实现中,我们把对 苹果的安排的实现 委托给了 AppleCheckExecutor(苹果检查执行器)AppleDispatcher 只对苹果的外观分数敏感,而不再关心苹果的命运。也就是说,除非以后公司修改对分数的划分,否则再也不需要修改 AppleDispatcher 里面的内容了

而在 AppleCheckExecutor 中,他又把具体的行为抛给了 AppleHandler(苹果处理器),他把苹果当作一个请求,让 AppleHandler 之间自己建立连接,他只关心如果没有处理器处理苹果的情况

也就是说在 AppleCheckExecutor 里面,苹果是这样流通的:

在这里插入图片描述


这种写法是有意义的,事实上 AppleCheckExecutor 根本不关心一个苹果到底经历了多少检测,又或者是在哪一个检测里被刷掉的;他关心的只有这个苹果最终有没有通过检测

也就是说,先检查外观,还是先检查虫洞,对 AppleCheckExecutor 来说完全无所谓。甚至你往里面添加新的检查内容,他都无所谓,你只需要新增 AppleHandler 的子类,然后再修改对应的执行链里面的构成就可以了

但这也被认为是职责链的缺陷之一,调用者对执行链的内容太不关心,导致对请求到底在哪里被拦截一无所知

而这正是一个标准的职责链实现



碎碎念

职责链和事件响应

现在几乎所有的GUI框架都是通过 事件监听器 的模式

即 一个事件在某个组件上发生后,组件会发布一条信息出来,当有监听器在监听这个事件时就会被调用(这是一种观察者思想的实现,观察者会在后续文章中登场,敬请期待

但问题是当 某个按钮和他身后的面板都有 单击事件监听器 的时候 ,我单击这个按钮,要怎么保证一定是最上方的按钮上的事件监听器被调用呢?

JavaScript中的事件响应处理就是这种思路,而他的解决方案就是职责链。把所有可以监听到当前事件的组件上注册的事件监听器组成一个职责链,然后把事件对象当作请求发送到这条职责链中。只要在职责链里被截获了, 那么后面的事件监听器就接收不到请求了(就像上例中的 WormHandler 截断请求后,AppearanceHandler 不会再进行处理一样)


职责链和组合

相信在上例的职责链实现中,你肯定对 AppleCheckExecutor 中的 root 产生了既视感

是的,这里就是用到了组合模式


事实上在实战中,职责链和组合基本都是成对出现的。因为职责链里面多个处理器需要被组合成前后项之间存在关联的一个整体,那这就可以被看成是一种树状结构

而树状结构用组合模式来实现是再合适不过的了


清晰的结构和复杂的代码

无论多牛的设计模式,都没逃过相同的宿命,即:

任何设计模式在让系统的结构更清晰的同时,也让代码变得更复杂

即使是结构最简单的单例模式或原型模式,也需要你书写比 A a = new A() 跟复杂的代码出来;但是我们在何时的情况下依然要使用他们,因为他们虽然让我们多写几行代码,但是他们对我们的系统友善,让我们把代码交给后辈时可以少挨两句骂

设计模式帮助你解耦,可是为了解耦,就一定要抽象一部分内容出来。在面向对象的环境下,抽象的最低粒度是接口

也就是说,原本可以写在一个函数里面的事情,现在你需要新建至少一个接口,并串联起他们。在这个过程中,代码的维护成本变高了,我们需要记住越来越多的类,理解越来越多对业务来说并没有什么卵用的——用于维护对象之间关系的代码

所以请让这些复杂的代码变得有意义,不要滥用设计模式

并不是什么时候都需要设计模式的,你可以为【Hello World】设计出什么模式呢?

但是如果就此认为我们研究设计模式毫无意义,又未免过于偏激。以前看过一本关于重构的书,书名已经记不起了,但是里面有段话印象深刻,大意如下:

你知道吗,其实大师级程序员和你之间的区别并不大。仅仅在于大师在开发系统的时候是在描述一个故事,他在描述这个系统应该具有什么样的行为;而你是在把脑海里的蓝图具体化成建筑。讲故事需要优雅的叙事手法,还有渊博的见闻作为佐料。而砌墙,需要的只是重复的搬砖

代码是艺术品,如果比喻成雕塑,设计模式就是你的板凿斧锯,没有这些东西,你做出来的作品注定称不上优雅

我只是希望你在某些应该用锉刀雕刻的细节上,不要用斧头去砍他




万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容


  1. QA,即 品质保证(Quality Assurance) ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/249734.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LEETCODE 170. 交易逆序对的总数

class Solution { public:int reversePairs(vector<int>& record) {if(record.size()<1)return 0;//归并 递归int left,right;left0;rightrecord.size()-1;int nummergeSort(left,right,record);return num;}int mergeSort(int left,int right, vector<int>…

C++:异常体系

异常体系 异常1.C语言传统的处理错误的方式2.C异常概念3.异常的使用3.1异常的抛出和捕获3.2 异常的重新抛出3.3异常安全3.4 异常规范 4.C标准库的异常体系5.异常的优缺点 异常 1.C语言传统的处理错误的方式 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以…

Android super.img解包和打包指南(含工具下载lpunpack、lpmake、lpdump)

本文所有命令均需要在linux 上执行 一、解包 1、将Android sparse image格式的super.img转成二进制文件 $ sudo apt install android-sdk-libsparse-utils $ simg2img super.img super.img.bin 2、下载工具lpunpack 和lpmake、lpdump 以及其依赖库 下载地址:https://downl…

python执行linux系统命令的三种方式

前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 1. 使用os.system 无法获取命令执行后的返回信息 import osos.system(ls)2. 使用os.popen 能够获取命令执行后的返回信息 impor…

c++学习第十四讲---STL常用容器---vector容器

vector容器&#xff1a; 1.vector基本概念&#xff1a; vector功能与数组类似&#xff0c;与数组不同的是&#xff0c;vector可以动态扩展。 2.vector构造函数&#xff1a; vector<T> v; //默认构造函数&#xff0c;创建数据类型T的容器 ve…

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析 【赛程名称】高职组-云计算赛项第一场-私有云【任务 1】私有云服务搭建[10 分]【题目 2】Yum 源配置[0.5 分]【题目 3】配置无秘钥 ssh[0.5 分]【题目 4】基础安装[0.5 分]【题目 5】数据库安装与调优[0.5 分]【题目 …

Cloudreve个人网盘系统源码 支持云存储(七牛、阿里云OSS、腾讯云COS、又拍云、OneDrive) 基于Go框架

现在的网盘动不动就限速&#xff0c;涨价&#xff0c;弄得很是心烦。今天分享一款开源免费的网盘项目&#xff0c;基于 Go 语言开发的 Cloudreve。Cloudreve基于Go框架云存储个人网盘系统源码支持多家云存储驱动&#xff08;从机、七牛、阿里云 OSS、腾讯云 COS、又拍云、OneDr…

05:容器镜像技术揭秘|发布容器服务器|私有镜像仓库

容器镜像技术揭秘&#xff5c;发布容器服务器&#xff5c;私有镜像仓库 创建镜像使用commit方法创建自定义镜像。Dockerfile打包镜像创建apache服务镜像制作 php 镜像 微服务架构创建nginx镜像 发布服务通过映射端口发布服务容器共享卷 docker私有仓库 创建镜像 使用commit方法…

vit细粒度图像分类(八)SIM-Trans学习笔记

1.摘要 细粒度视觉分类(FGVC)旨在从相似的从属类别中识别物体&#xff0c;这对人类准确的自动识别需求具有挑战性和实用性。大多数FGVC方法侧重于判别区域挖掘的注意机制研究&#xff0c;而忽略了它们之间的相互依赖关系和组成的整体对象结构&#xff0c;而这些对模型的判别信…

vue3 [Vue warn]: Unhandled error during execution of scheduler flush

文章目录 前言一、报错截图二、排除问题思路相关问题 Vue3 优雅解决方法异步组件异同之处&#xff1a;好处&#xff1a;在使用异步组件时&#xff0c;有几个注意点&#xff1a; vue3 定义与使用异步组件 总结 前言 Bug 记录。开发环境运行正常&#xff0c;构建后时不时触发下面…

基于C/C++的MFC的IDC_MFCEDITBROWSE2控件不显示ico问题记录

打开资源文件 *.rc文件 &#xff0c;在最上方添加 #if !defined(_AFXDLL) #include "afxribbon.rc" // MFC ribbon and control bar resources #endif 如下图所示&#xff1a;

【SpringCloud】使用OpenFeign进行微服务化改造

目录 一、需求与背景二、OpenFeign 远程调用技术原理三、项目代码演示3.1 引入依赖3.2 实现OpenFeign注解修饰接口3.3 指定 OpenFeign 远程调用接口的扫描路径 四、OpenFeign 在日志中打印Request和Response五、OpenFeign 客户端超时配置六、使用 OpenFeign 实现服务降级6.1 实…

C语言数据结构之二叉树

少年恃险若平地 独倚长剑凌清秋 &#x1f3a5;烟雨长虹&#xff0c;孤鹜齐飞的个人主页 &#x1f525;个人专栏 &#x1f3a5;前期回顾-栈和队列 期待小伙伴们的支持与关注&#xff01;&#xff01;&#xff01; 目录 树的定义与判定 树的定义 树的判定 树的相关概念 树的运用…

网络编程套接字(2)

TCP 简单的TCP网络程序服务端创建套接字 服务端绑定服务端监听服务端接收连接测试服务端处理请求客户端创建套接字客户端连接服务器客户端连接服务器单执行流的服务器客户端为什么会显示连接成功&#xff1f; 多进程版的TCP网络程序让孙子进程提供服务 多线程版的TCP网络程序 简…

设计模式——模板方法模式(Template Method Pattern)

概述 模板方法模式&#xff1a;定义一个操作中算法的框架&#xff0c;而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法模式是一种基于继承的代码复用技术&#xff0c;它是一种类行为型模式。模板方法模式是结…

基于node.js和Vue3的医院挂号就诊住院信息管理系统

摘要&#xff1a; 随着信息技术的快速发展&#xff0c;医院挂号就诊住院信息管理系统的构建变得尤为重要。该系统旨在提供一个高效、便捷的医疗服务平台&#xff0c;以改善患者就医体验和提高医院工作效率。本系统基于Node.js后端技术和Vue3前端框架进行开发&#xff0c;利用其…

【Emgu CV教程】6.8、图像平滑之BilateralFilter()双边滤波

文章目录 一、介绍1.原理2.函数介绍 二、举例1.原始素材2.代码3.运行结果 一、介绍 1.原理 BilateralFilter()双边滤波也是非线性滤波&#xff0c;之前介绍的滤波只考虑空间信息&#xff08;滤波核或邻域&#xff09;&#xff0c;容易造成边缘模糊和细节丢失&#xff0c;相比…

在Windows系统中执行DOS命令

目录 一、用菜单的形式进入DOS窗口 二、通过IE浏览器访问DOS窗口 三、复制、粘贴命令行 四、设置窗口风格 1.颜色 2.字体 3.布局 4.选项 五、Windows系统命令行 由于Windows系统彻底脱离了DOS操作系统&#xff0c;所以无法直接进入DOS环境&#xff0c;只能通过第三方软…

UE4学习笔记 FPS游戏制作3 添加武器

文章目录 章节目标为骨骼添加武器挂载点添加武器 章节目标 本章节为手部添加一个武器挂载点&#xff0c;并挂载一个武器 为骨骼添加武器挂载点 添加挂载点需要以一个动画片段为基础&#xff0c;为骨骼添加挂载点。 首先找到我们需要的动画片段&#xff0c;通常是idle 双击打…

c++设计模式之观察者模式(发布-订阅模式)

介绍 观察者模式主要关注于对象的一对多关系&#xff0c;其中多个对象都依赖于一个对象&#xff0c;当该对象的状态发生改变时&#xff0c;其余对象都能接收到相应的通知。 如&#xff0c;现在有 一个数据对象三个画图对象&#xff0c;分别wield曲线图、柱状图、饼状图三个对象…