同事写了一个责任链模式,bug无数...
文章来源:https://c1n.cn/dilnW
目录
背景
什么是责任链
使用场景
结语
背景
最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。
实际上,针对导入功能,我认为模版方法更合适!为此,隔壁团队也拿出我们的案例,进行了集体 code review。
学好设计模式,且不要为了练习,强行使用!让原本 100 行就能实现的功能,写了 3000 行!对错暂且不论,我们先一起看看责任链设计模式吧!
什么是责任链
责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。
使用场景
责任链的使用场景还是比较多的:
多条件流程判断:权限控制
ERP 系统流程审批:总经理、人事经理、项目经理
Java 过滤器的底层实现 Filter
如果不使用该设计模式,那么当需求有所改变时,就会使得代码臃肿或者难以维护,例如下面的例子。
| 反例
假设现在有一个闯关游戏,进入下一关的条件是上一关的分数要高于 xx:
游戏一共 3 个关卡
进入第二关需要第一关的游戏得分大于等于 80
进入第三关需要第二关的游戏得分大于等于 90
那么代码可以这样写:
//第一关
public class FirstPassHandler {public int handler(){System.out.println("第一关-->FirstPassHandler");return 80;}
}//第二关
public class SecondPassHandler {public int handler(){System.out.println("第二关-->SecondPassHandler");return 90;}
}//第三关
public class ThirdPassHandler {public int handler(){System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");return 95;}
}//客户端
public class HandlerClient {public static void main(String[] args) {FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关int firstScore = firstPassHandler.handler();//第一关的分数大于等于80则进入第二关if(firstScore >= 80){int secondScore = secondPassHandler.handler();//第二关的分数大于等于90则进入第二关if(secondScore >= 90){thirdPassHandler.handler();}}}
}
那么如果这个游戏有 100 关,我们的代码很可能就会写成这个样子:
if(第1关通过){// 第2关 游戏if(第2关通过){// 第3关 游戏if(第3关通过){// 第4关 游戏if(第4关通过){// 第5关 游戏if(第5关通过){// 第6关 游戏if(第6关通过){//...}}} }}
}
这种代码不仅冗余,并且当我们要将某两关进行调整时会对代码非常大的改动,这种操作的风险是很高的,因此,该写法非常糟糕。
| 初步改造
如何解决这个问题,我们可以通过链表将每一关连接起来,形成责任链的方式,第一关通过后是第二关,第二关通过后是第三关....
这样客户端就不需要进行多重 if 的判断了:
public class FirstPassHandler {/*** 第一关的下一关是 第二关*/private SecondPassHandler secondPassHandler;public void setSecondPassHandler(SecondPassHandler secondPassHandler) {this.secondPassHandler = secondPassHandler;}//本关卡游戏得分private int play(){return 80;}public int handler(){System.out.println("第一关-->FirstPassHandler");if(play() >= 80){//分数>=80 并且存在下一关才进入下一关if(this.secondPassHandler != null){return this.secondPassHandler.handler();}}return 80;}
}public class SecondPassHandler {/*** 第二关的下一关是 第三关*/private ThirdPassHandler thirdPassHandler;public void setThirdPassHandler(ThirdPassHandler thirdPassHandler) {this.thirdPassHandler = thirdPassHandler;}//本关卡游戏得分private int play(){return 90;}public int handler(){System.out.println("第二关-->SecondPassHandler");if(play() >= 90){//分数>=90 并且存在下一关才进入下一关if(this.thirdPassHandler != null){return this.thirdPassHandler.handler();}}return 90;}
}public class ThirdPassHandler {//本关卡游戏得分private int play(){return 95;}/*** 这是最后一关,因此没有下一关*/public int handler(){System.out.println("第三关-->ThirdPassHandler,这是最后一关啦");return play();}
}public class HandlerClient {public static void main(String[] args) {FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关firstPassHandler.setSecondPassHandler(secondPassHandler);//第一关的下一关是第二关secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二关的下一关是第三关//说明:因为第三关是最后一关,因此没有下一关//开始调用第一关 每一个关卡是否进入下一关卡 在每个关卡中判断firstPassHandler.handler();}
}
| 缺点
现有模式的缺点:
每个关卡中都有下一关的成员变量并且是不一样的,形成链很不方便
代码的扩展性非常不好
| 责任链改造
既然每个关卡中都有下一关的成员变量并且是不一样的,那么我们可以在关卡上抽象出一个父类或者接口,然后每个具体的关卡去继承或者实现。
有了思路,我们先来简单介绍一下责任链设计模式的基本组成:
抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
public abstract class AbstractHandler {/*** 下一关用当前抽象类来接收*/protected AbstractHandler next;public void setNext(AbstractHandler next) {this.next = next;}public abstract int handler();
}public class FirstPassHandler extends AbstractHandler{private int play(){return 80;}@Overridepublic int handler(){System.out.println("第一关-->FirstPassHandler");int score = play();if(score >= 80){//分数>=80 并且存在下一关才进入下一关if(this.next != null){return this.next.handler();}}return score;}
}public class SecondPassHandler extends AbstractHandler{private int play(){return 90;}public int handler(){System.out.println("第二关-->SecondPassHandler");int score = play();if(score >= 90){//分数>=90 并且存在下一关才进入下一关if(this.next != null){return this.next.handler();}}return score;}
}public class ThirdPassHandler extends AbstractHandler{private int play(){return 95;}public int handler(){System.out.println("第三关-->ThirdPassHandler");int score = play();if(score >= 95){//分数>=95 并且存在下一关才进入下一关if(this.next != null){return this.next.handler();}}return score;}
}public class HandlerClient {public static void main(String[] args) {FirstPassHandler firstPassHandler = new FirstPassHandler();//第一关SecondPassHandler secondPassHandler = new SecondPassHandler();//第二关ThirdPassHandler thirdPassHandler = new ThirdPassHandler();//第三关// 和上面没有更改的客户端代码相比,只有这里的set方法发生变化,其他都是一样的firstPassHandler.setNext(secondPassHandler);//第一关的下一关是第二关secondPassHandler.setNext(thirdPassHandler);//第二关的下一关是第三关//说明:因为第三关是最后一关,因此没有下一关//从第一个关卡开始firstPassHandler.handler();}
}
| 责任链工厂改造
对于上面的请求链,我们也可以把这个关系维护到配置文件中或者一个枚举中。我将使用枚举来教会大家怎么动态的配置请求链并且将每个请求者形成一条调用链。
public enum GatewayEnum {// handlerId, 拦截者名称,全限定类名,preHandlerId,nextHandlerIdAPI_HANDLER(new GatewayEntity(1, "api接口限流", "cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler", null, 2)),BLACKLIST_HANDLER(new GatewayEntity(2, "黑名单拦截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler", 1, 3)),SESSION_HANDLER(new GatewayEntity(3, "用户会话拦截", "cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler", 2, null)),;GatewayEntity gatewayEntity;public GatewayEntity getGatewayEntity() {return gatewayEntity;}GatewayEnum(GatewayEntity gatewayEntity) {this.gatewayEntity = gatewayEntity;}
}public class GatewayEntity {private String name;private String conference;private Integer handlerId;private Integer preHandlerId;private Integer nextHandlerId;
}public interface GatewayDao {/*** 根据 handlerId 获取配置项* @param handlerId* @return*/GatewayEntity getGatewayEntity(Integer handlerId);/*** 获取第一个处理者* @return*/GatewayEntity getFirstGatewayEntity();
}public class GatewayImpl implements GatewayDao {/*** 初始化,将枚举中配置的handler初始化到map中,方便获取*/private static Map<Integer, GatewayEntity> gatewayEntityMap = new HashMap<>();static {GatewayEnum[] values = GatewayEnum.values();for (GatewayEnum value : values) {GatewayEntity gatewayEntity = value.getGatewayEntity();gatewayEntityMap.put(gatewayEntity.getHandlerId(), gatewayEntity);}}@Overridepublic GatewayEntity getGatewayEntity(Integer handlerId) {return gatewayEntityMap.get(handlerId);}@Overridepublic GatewayEntity getFirstGatewayEntity() {for (Map.Entry<Integer, GatewayEntity> entry : gatewayEntityMap.entrySet()) {GatewayEntity value = entry.getValue();// 没有上一个handler的就是第一个if (value.getPreHandlerId() == null) {return value;}}return null;}
}public class GatewayHandlerEnumFactory {private static GatewayDao gatewayDao = new GatewayImpl();// 提供静态方法,获取第一个handlerpublic static GatewayHandler getFirstGatewayHandler() {GatewayEntity firstGatewayEntity = gatewayDao.getFirstGatewayEntity();GatewayHandler firstGatewayHandler = newGatewayHandler(firstGatewayEntity);if (firstGatewayHandler == null) {return null;}GatewayEntity tempGatewayEntity = firstGatewayEntity;Integer nextHandlerId = null;GatewayHandler tempGatewayHandler = firstGatewayHandler;// 迭代遍历所有handler,以及将它们链接起来while ((nextHandlerId = tempGatewayEntity.getNextHandlerId()) != null) {GatewayEntity gatewayEntity = gatewayDao.getGatewayEntity(nextHandlerId);GatewayHandler gatewayHandler = newGatewayHandler(gatewayEntity);tempGatewayHandler.setNext(gatewayHandler);tempGatewayHandler = gatewayHandler;tempGatewayEntity = gatewayEntity;}// 返回第一个handlerreturn firstGatewayHandler;}/*** 反射实体化具体的处理者* @param firstGatewayEntity* @return*/private static GatewayHandler newGatewayHandler(GatewayEntity firstGatewayEntity) {// 获取全限定类名String className = firstGatewayEntity.getConference(); try {// 根据全限定类名,加载并初始化该类,即会初始化该类的静态段Class<?> clazz = Class.forName(className);return (GatewayHandler) clazz.newInstance();} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {e.printStackTrace();}return null;}}public class GetewayClient {public static void main(String[] args) {GetewayHandler firstGetewayHandler = GetewayHandlerEnumFactory.getFirstGetewayHandler();firstGetewayHandler.service();}
}
结语
设计模式有很多,责任链只是其中的一种,我觉得很有意思,非常值得一学。设计模式确实是一门艺术,仍需努力呀!
------------- END -------------
扫码免费获取600+页石杉老师原创精品文章汇总PDF原创技术文章汇总点个在看你最好看
同事写了一个责任链模式,bug无数...相关推荐
- 分享一个责任链模式通用写法
方式一 思路 责任链模式,是一个单向链表结构,所以需要记录下一个责任链 通过构造器的链式编程方便添加责任链 每次添加责任链,都会将最新的责任链记录到构造器里,这里无法找到链首,所以,单独定义一个变量存 ...
- 最近学习了责任链模式
2019独角兽企业重金招聘Python工程师标准>>> 前言 来菜鸟这个大家庭10个月了,总得来说比较融入了环境,同时在忙碌的工作中也深感技术积累不够,在优秀的人身边工作必须更加花时 ...
- 设计模式之略见一斑(Chain of Responsibility责任链模式)
设计模式 写道 面向对象开发人员通常希望明确和减少对象间的责任,从而降低对象之间的耦合程序.这样我们的系统更加容易修改,同时也可降低产生缺陷的风险.从某种程度上说,java语言本身能够帮助降低对象间的 ...
- 行为型设计模式(1)—— 责任链模式(Chain of Responsibility Pattern)
文章目录 1.简介 2.使用场景 3.示例 4.变种 参考文献 1.简介 经常听身边的同事说其在项目中用到了责任链模式,今天就来学习一下什么是责任链模式. 责任链模式(Chain of Respons ...
- 轻松学习Java设计模式之责任链模式
我们的态度是:每天进步一点点,理想终会被实现. 前言 设计模式,可能很多人都是看到代码知道怎么回事,但是离开代码再让其说出来,估计就有点含糊其词了,包括我自己在内.Android中其实用到的设计模式也 ...
- 一篇文章搞懂Java设计模式之责任链模式
简述: 前端时间再看一些类库的源码,发现责任链模式的强大之处,尤其是和建造者模式的结合后强大的动态可扩展性更是牛逼的一塌糊涂.接下来赶紧了解一下吧! 我们先来了解一下什么是责任链模式: 职责链模式(C ...
- java设计模式---责任链模式详解
深入理解什么是责任链模式 一,责任链模式 1,什么是责任链模式 二,框架中使用到的责任链模式 1,springmvc流程 2,mybatis的执行流程 3,spring的过滤器和拦截器 4,senti ...
- 设计模式之五 责任链模式(Chain of Responsibility)
2019独角兽企业重金招聘Python工程师标准>>> 一. 场景 相信我们都有过这样的经历: 我们去职能部门办理一个事情,先去了A部门,到了地方被告知这件事情由B部门处理: 当我们 ...
- [转]《JAVA与模式》之责任链模式
http://www.cnblogs.com/java-my-life/archive/2012/05/28/2516865.html 在阎宏博士的<JAVA与模式>一书中开头是这样描述责 ...
最新文章
- Nginx开启gzip压缩解决react打包文件过大
- 如何自学python数据分析-Python学习干货 |如何用Python进行数据分析?
- 某网SQL注入漏洞实战
- FPGA进阶篇--SPI控制双通道16bit串行DAC8532
- ie6、7 下input的边框问题 ?
- thinkphp使用echarts_Thinkphp 与Echarts-php 使用
- 《企业软件交付:敏捷与高效管理精要》——3.4 企业软件交付的软件工厂方法...
- 空格在科技类文章中对阅读体验的影响
- Atitit Spring事务配置不起作用可能出现的问题: .是否是数据库引擎设置不对造成的【笔者就遇到了这个问题,由于笔者使用的是mysql数据,但是在创建表的时候引擎默认(mysql中引擎默认为
- 使用Spine来完成骨骼动画
- 网上支付跨行清算系统与大小额支付系统有什么区别?
- 目标客户画像_做营销时,如何做好目标用户群体画像?
- 车型识别API调用对比
- java基础知识入门大全(十年经验总结)
- c 朗读html,朗读《送杜少府之任蜀》
- android Studio Crunching Cruncher
- 详解 gRPC 客户端长连接机制实现
- U盘文件无损进行格式转换
- 南邮 OJ 1567 Suspicious Stocks
- SP申请业务方案编写模板
热门文章
- 开源私有lorawan server搭建
- Android 11.0 12.0遥控器点击输入框 弹不出输入法
- python sns画布大小设置
- 工作站压力测试软件,胜任多种工作负载 联想P500工作站评测
- js数组的方法和扩展运算符
- 虚拟机十步安装VMware_workstation
- 收货地址 (默认收货地址)
- php 统计文章字符,PHP统计文章内容字符数
- 物理专业要用的计算机语言,16岁被保送清华,本科毕业进麻省理工读博,现开发Taichi爆红网络...
- html中并列式的应用,并列式结构梳理