位置: IT常识 - 正文

同事写了一个责任链模式,bug 无数...

编辑:rootadmin
背景 最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。 实际上,针对导入功能,我认为模版方法更合适!为此,隔壁团队也拿出我们的案例,进行了集体 code review。 学好设计模式,且不要为了练习,强行使用!让原本 100 行就能实 ... 背景

推荐整理分享同事写了一个责任链模式,bug 无数...,希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:,内容如对您有帮助,希望把文章链接给更多的朋友!

最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,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(); } } }}同事写了一个责任链模式,bug 无数...

那么如果这个游戏有 100 关,我们的代码很可能就会写成这个样子:

if(第1关通过){ // 第2关 游戏 if(第2关通过){ // 第3关 游戏 if(第3关通过){ // 第4关 游戏 if(第4关通过){ // 第5关 游戏 if(第5关通过){ // 第6关 游戏 if(第6关通过){ //... } } } } }}

这种代码不仅冗余,并且当我们要将某两关进行调整时会对代码非常大的改动,这种操作的风险是很高的,因此,该写法非常糟糕。

更多设计模式系列教程:https://www.javastack.cn/

| 初步改造

如何解决这个问题,我们可以通过链表将每一关连接起来,形成责任链的方式,第一关通过后是第二关,第二关通过后是第三关....

这样客户端就不需要进行多重 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; } @Override public 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,nextHandlerId API_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); } } @Override public GatewayEntity getGatewayEntity(Integer handlerId) { return gatewayEntityMap.get(handlerId); } @Override public 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(); // 提供静态方法,获取第一个handler public 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; } // 返回第一个handler return 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(); }}

设计模式有很多,责任链只是其中的一种,我觉得很有意思,非常值得一学。设计模式确实是一门艺术,仍需努力呀!

版权

本文链接地址:https://www.jiuchutong.com/zhishi/304792.html 转载请保留说明!

上一篇:一起爬山吗?《隐秘的角落》为什么译成Cat's Cradle?(你要和我一起爬山吗)

下一篇:php使用array_diff去除元素(php使用while循环计算1到100的和)

  • 微博怎么查看浏览记录(微博怎么查看浏览量)

    微博怎么查看浏览记录(微博怎么查看浏览量)

  • 企业微信怎么切换账号(企业微信怎么切换实名认证)

    企业微信怎么切换账号(企业微信怎么切换实名认证)

  • 微信朋友权限是什么意思(微信朋友权限是仅聊天,是不是看不到他朋友圈了)

    微信朋友权限是什么意思(微信朋友权限是仅聊天,是不是看不到他朋友圈了)

  • 微信朋友圈双击头像震动是什么意思(微信朋友圈双击点赞)

    微信朋友圈双击头像震动是什么意思(微信朋友圈双击点赞)

  • 拼多多是以什么模式形成规模效应的(拼多多是以什么形式规模效应的)

    拼多多是以什么模式形成规模效应的(拼多多是以什么形式规模效应的)

  • 抖音粉丝通知页在哪里

    抖音粉丝通知页在哪里

  • 为什么人脸识别不能通过(为什么人脸识别不了)

    为什么人脸识别不能通过(为什么人脸识别不了)

  • 电池鼓包能刺破放气吗(电池鼓包能刺破继续用)

    电池鼓包能刺破放气吗(电池鼓包能刺破继续用)

  • ios12.4屏蔽更新描述文件失效(ios12.4.8屏蔽更新)

    ios12.4屏蔽更新描述文件失效(ios12.4.8屏蔽更新)

  • 腾讯会议声音关不掉(腾讯会议声音关小)

    腾讯会议声音关不掉(腾讯会议声音关小)

  • ppt怎么设置切换时间(PPT怎么设置切换动画)

    ppt怎么设置切换时间(PPT怎么设置切换动画)

  • SQL语言的功能包括(sql语言的功能包括数据查询)

    SQL语言的功能包括(sql语言的功能包括数据查询)

  • 美版xsmax支持电信吗(美版xsmax支持移动4g吗)

    美版xsmax支持电信吗(美版xsmax支持移动4g吗)

  • iphonex是iphone9吗(iPhonex是iPhone9吗)

    iphonex是iphone9吗(iPhonex是iPhone9吗)

  • qq以前的照片在哪里找(qq里以前的照片在哪里可以找到)

    qq以前的照片在哪里找(qq里以前的照片在哪里可以找到)

  • 图形的表示方法有两种(图形表示的两种方式)

    图形的表示方法有两种(图形表示的两种方式)

  • ps如何处理扫描文件(ps如何处理扫描的pcb板图片视频)

    ps如何处理扫描文件(ps如何处理扫描的pcb板图片视频)

  • win10开机启动项怎么设置(win10开机启动项文件夹路径)

    win10开机启动项怎么设置(win10开机启动项文件夹路径)

  • 扫码付款怎么操作(扫码付款怎么操作的视频)

    扫码付款怎么操作(扫码付款怎么操作的视频)

  • 低数据模式是啥意思(低数据模式开与不开的区别)

    低数据模式是啥意思(低数据模式开与不开的区别)

  • 人言可畏是什么意思(人言可畏是什么生肖比较好)

    人言可畏是什么意思(人言可畏是什么生肖比较好)

  • 如何往kindle里导入书籍(如何往kindle里导入电子书)

    如何往kindle里导入书籍(如何往kindle里导入电子书)

  • 手机摄像头抖动(手机摄像头抖动用吸铁石)

    手机摄像头抖动(手机摄像头抖动用吸铁石)

  • 查询iphone激活时间(ihone查询激活时间)

    查询iphone激活时间(ihone查询激活时间)

  • 耳机右边声音小(耳机右边声音小怎么调)

    耳机右边声音小(耳机右边声音小怎么调)

  • 注册公司不开账户可以吗
  • 个人所得税的标准计算
  • 桥闸通行费怎么做账务处理
  • 存在弃置费用的固定资产的财务费用
  • 期末结转的内容
  • 房地产土地使用税纳税义务发生时间
  • 礼品费如何报销
  • 发票遗失如何报销
  • 政府非税收入的种类
  • 个人独资企业500万以下
  • 企业股权资本的组成
  • 库存商品内部划转怎么做账?
  • 进项税抵扣销项税
  • 建设工程项目融资
  • 药店药品成本怎么核算
  • 公司多久不做账会被注销?
  • 人工服务费发票
  • 个体工商户投资者减除费用
  • 暂估入账没有调增,以后年度怎么调整
  • 报税营业成本包括管理费用吗
  • 医院医保统筹支付后还能报销吗
  • 外省工程需要什么手续
  • 非绑定账户转入啥意思
  • 租车公司的车能租吗
  • 认证成功次月何时补发
  • mac苹果电脑系统
  • mac qq音乐怎么下载mp3格式的音乐
  • 苹果键盘快捷键怎么用
  • 员工用差旅费冲账怎么办
  • 门店出纳的工作内容
  • 饭店开业请客说什么
  • php 字符串函数
  • window10黑白模式
  • 共管账户和监管账户是一样的吗知乎
  • 预缴增值税少交了
  • vue设置图片
  • yarn的配置
  • thinkphp 多数据库
  • 支付劳务公司的劳务费计入应付账款还是其他应付款
  • 劳务公司给包工头打款备注写什么
  • laravel datatable
  • 个体工商户使用个人二维码收款违法吗
  • 推荐 4个有趣实践作业
  • cvpr2020目标跟踪
  • php基于单例模式开发
  • 国家对建筑垃圾处理企业有补助吗
  • 企业内部研发支持方案
  • sql server 2008登录服务器名称
  • mysql5.5数据库安装教程
  • 辞退补偿金的标准
  • 应收账款是指的资产吗
  • 商贸企业 税收 政策规定
  • 个体工商户税收新政策
  • 固定资产折旧年限
  • 广告公司收到广告收入会计分录
  • 个人所得税多计算了一个月工资怎么办
  • 未认证的进项税额转出会计分录
  • 销售免税货物的会计分录
  • win8系统安装教程图解
  • 苹果mac无法连接网络
  • win10睡眠什么意思
  • macbook air一键恢复出厂设置
  • ultra系统优盘恢复
  • Linux系统安全配置包括
  • win7游戏打开没反应
  • win7系统aero主题变成黑色
  • 在linux系统中,用来存放系统所需要的配置文件
  • bat脚本ftp上传文件
  • vue自定义过滤器的语法
  • unicode编码实现方案
  • Javascript中的this
  • jquery 动态绑定click事件
  • 安卓开发常用代码
  • 基于javascript的毕业设计选题
  • 如何将文件夹导入idea
  • 税务机关代收工会经费手续费
  • 销售旧货如何开票
  • 地税局一般几点下班
  • 欠账6万怎么还
  • 非中山户籍学生可以自己买医保吗
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设