什么是责任链
使用场景
反例
初步改造
缺点
责任链改造
责任链工厂改造
结语
最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。
实际上,针对导入功能,我认为模版方法更合适!为此,隔壁团队也拿出我们的案例,进行了集体 code review。
学好设计模式,且不要为了练习,强行使用!让原本 100 行就能实现的功能,写了 3000 行!对错暂且不论,我们先一起看看责任链设计模式吧!
什么是责任链
责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
视频教程:https://doc.iocoder.cn/video/
使用场景
责任链的使用场景还是比较多的:
多条件流程判断:权限控制
ERP 系统流程审批:总经理、人事经理、项目经理
如果不使用该设计模式,那么当需求有所改变时,就会使得代码臃肿或者难以维护,例如下面的例子。
反例
假设现在有一个闯关游戏,进入下一关的条件是上一关的分数要高于 xx:
游戏一共 3 个关卡
进入第二关需要第一关的游戏得分大于等于 80
进入第三关需要第二关的游戏得分大于等于 90
那么代码可以这样写:
//第一关 publicclassFirstPassHandler{ publicinthandler(){ System.out.println("第一关-->FirstPassHandler"); return80; } } //第二关 publicclassSecondPassHandler{ publicinthandler(){ System.out.println("第二关-->SecondPassHandler"); return90; } } //第三关 publicclassThirdPassHandler{ publicinthandler(){ System.out.println("第三关-->ThirdPassHandler,这是最后一关啦"); return95; } } //客户端 publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一关 SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二关 ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三关 intfirstScore=firstPassHandler.handler(); //第一关的分数大于等于80则进入第二关 if(firstScore>=80){ intsecondScore=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 的判断了:
publicclassFirstPassHandler{ /** *第一关的下一关是第二关 */ privateSecondPassHandlersecondPassHandler; publicvoidsetSecondPassHandler(SecondPassHandlersecondPassHandler){ this.secondPassHandler=secondPassHandler; } //本关卡游戏得分 privateintplay(){ return80; } publicinthandler(){ System.out.println("第一关-->FirstPassHandler"); if(play()>=80){ //分数>=80并且存在下一关才进入下一关 if(this.secondPassHandler!=null){ returnthis.secondPassHandler.handler(); } } return80; } } publicclassSecondPassHandler{ /** *第二关的下一关是第三关 */ privateThirdPassHandlerthirdPassHandler; publicvoidsetThirdPassHandler(ThirdPassHandlerthirdPassHandler){ this.thirdPassHandler=thirdPassHandler; } //本关卡游戏得分 privateintplay(){ return90; } publicinthandler(){ System.out.println("第二关-->SecondPassHandler"); if(play()>=90){ //分数>=90并且存在下一关才进入下一关 if(this.thirdPassHandler!=null){ returnthis.thirdPassHandler.handler(); } } return90; } } publicclassThirdPassHandler{ //本关卡游戏得分 privateintplay(){ return95; } /** *这是最后一关,因此没有下一关 */ publicinthandler(){ System.out.println("第三关-->ThirdPassHandler,这是最后一关啦"); returnplay(); } } publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一关 SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二关 ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三关 firstPassHandler.setSecondPassHandler(secondPassHandler);//第一关的下一关是第二关 secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二关的下一关是第三关 //说明:因为第三关是最后一关,因此没有下一关 //开始调用第一关每一个关卡是否进入下一关卡在每个关卡中判断 firstPassHandler.handler(); } }
缺点
现有模式的缺点:
每个关卡中都有下一关的成员变量并且是不一样的,形成链很不方便
代码的扩展性非常不好
责任链改造
既然每个关卡中都有下一关的成员变量并且是不一样的,那么我们可以在关卡上抽象出一个父类或者接口,然后每个具体的关卡去继承或者实现。
有了思路,我们先来简单介绍一下责任链设计模式的基本组成:
抽象处理者(Handler)角色: 定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者(Concrete Handler)角色: 实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色: 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
publicabstractclassAbstractHandler{ /** *下一关用当前抽象类来接收 */ protectedAbstractHandlernext; publicvoidsetNext(AbstractHandlernext){ this.next=next; } publicabstractinthandler(); } publicclassFirstPassHandlerextendsAbstractHandler{ privateintplay(){ return80; } @Override publicinthandler(){ System.out.println("第一关-->FirstPassHandler"); intscore=play(); if(score>=80){ //分数>=80并且存在下一关才进入下一关 if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassSecondPassHandlerextendsAbstractHandler{ privateintplay(){ return90; } publicinthandler(){ System.out.println("第二关-->SecondPassHandler"); intscore=play(); if(score>=90){ //分数>=90并且存在下一关才进入下一关 if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassThirdPassHandlerextendsAbstractHandler{ privateintplay(){ return95; } publicinthandler(){ System.out.println("第三关-->ThirdPassHandler"); intscore=play(); if(score>=95){ //分数>=95并且存在下一关才进入下一关 if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一关 SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二关 ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三关 //和上面没有更改的客户端代码相比,只有这里的set方法发生变化,其他都是一样的 firstPassHandler.setNext(secondPassHandler);//第一关的下一关是第二关 secondPassHandler.setNext(thirdPassHandler);//第二关的下一关是第三关 //说明:因为第三关是最后一关,因此没有下一关 //从第一个关卡开始 firstPassHandler.handler(); } }
责任链工厂改造
对于上面的请求链,我们也可以把这个关系维护到配置文件中或者一个枚举中。我将使用枚举来教会大家怎么动态的配置请求链并且将每个请求者形成一条调用链。
publicenumGatewayEnum{ //handlerId,拦截者名称,全限定类名,preHandlerId,nextHandlerId API_HANDLER(newGatewayEntity(1,"api接口限流","cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler",null,2)), BLACKLIST_HANDLER(newGatewayEntity(2,"黑名单拦截","cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler",1,3)), SESSION_HANDLER(newGatewayEntity(3,"用户会话拦截","cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler",2,null)), ; GatewayEntitygatewayEntity; publicGatewayEntitygetGatewayEntity(){ returngatewayEntity; } GatewayEnum(GatewayEntitygatewayEntity){ this.gatewayEntity=gatewayEntity; } } publicclassGatewayEntity{ privateStringname; privateStringconference; privateIntegerhandlerId; privateIntegerpreHandlerId; privateIntegernextHandlerId; } publicinterfaceGatewayDao{ /** *根据handlerId获取配置项 *@paramhandlerId *@return */ GatewayEntitygetGatewayEntity(IntegerhandlerId); /** *获取第一个处理者 *@return */ GatewayEntitygetFirstGatewayEntity(); } publicclassGatewayImplimplementsGatewayDao{ /** *初始化,将枚举中配置的handler初始化到map中,方便获取 */ privatestaticMapgatewayEntityMap=newHashMap<>(); static{ GatewayEnum[]values=GatewayEnum.values(); for(GatewayEnumvalue:values){ GatewayEntitygatewayEntity=value.getGatewayEntity(); gatewayEntityMap.put(gatewayEntity.getHandlerId(),gatewayEntity); } } @Override publicGatewayEntitygetGatewayEntity(IntegerhandlerId){ returngatewayEntityMap.get(handlerId); } @Override publicGatewayEntitygetFirstGatewayEntity(){ for(Map.Entry entry:gatewayEntityMap.entrySet()){ GatewayEntityvalue=entry.getValue(); //没有上一个handler的就是第一个 if(value.getPreHandlerId()==null){ returnvalue; } } returnnull; } } publicclassGatewayHandlerEnumFactory{ privatestaticGatewayDaogatewayDao=newGatewayImpl(); //提供静态方法,获取第一个handler publicstaticGatewayHandlergetFirstGatewayHandler(){ GatewayEntityfirstGatewayEntity=gatewayDao.getFirstGatewayEntity(); GatewayHandlerfirstGatewayHandler=newGatewayHandler(firstGatewayEntity); if(firstGatewayHandler==null){ returnnull; } GatewayEntitytempGatewayEntity=firstGatewayEntity; IntegernextHandlerId=null; GatewayHandlertempGatewayHandler=firstGatewayHandler; //迭代遍历所有handler,以及将它们链接起来 while((nextHandlerId=tempGatewayEntity.getNextHandlerId())!=null){ GatewayEntitygatewayEntity=gatewayDao.getGatewayEntity(nextHandlerId); GatewayHandlergatewayHandler=newGatewayHandler(gatewayEntity); tempGatewayHandler.setNext(gatewayHandler); tempGatewayHandler=gatewayHandler; tempGatewayEntity=gatewayEntity; } //返回第一个handler returnfirstGatewayHandler; } /** *反射实体化具体的处理者 *@paramfirstGatewayEntity *@return */ privatestaticGatewayHandlernewGatewayHandler(GatewayEntityfirstGatewayEntity){ //获取全限定类名 StringclassName=firstGatewayEntity.getConference(); try{ //根据全限定类名,加载并初始化该类,即会初始化该类的静态段 Class>clazz=Class.forName(className); return(GatewayHandler)clazz.newInstance(); }catch(ClassNotFoundException|IllegalAccessException|InstantiationExceptione){ e.printStackTrace(); } returnnull; } } publicclassGetewayClient{ publicstaticvoidmain(String[]args){ GetewayHandlerfirstGetewayHandler=GetewayHandlerEnumFactory.getFirstGetewayHandler(); firstGetewayHandler.service(); } }
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
项目地址:https://github.com/YunaiV/yudao-cloud
视频教程:https://doc.iocoder.cn/video/
结语
设计模式有很多,责任链只是其中的一种,我觉得很有意思,非常值得一学。设计模式确实是一门艺术,仍需努力呀!
-
程序
+关注
关注
117文章
3798浏览量
81501 -
代码
+关注
关注
30文章
4841浏览量
69197 -
RBAC
+关注
关注
0文章
44浏览量
9998
原文标题:代码精简10倍,责任链模式yyds
文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
为什么使用菊花链配置模式对FPGA编程会失败?
嵌入式系统的知识平台与平台模式详解
电子行业供应链责任向前迈出一步
区块链技术有哪些特点_区块链技术应用_区块链技术的工作原理
半导体芯片行业的运作模式是什么(IDM/Fabless/Foundry模式)
一起看看责任链设计模式吧!
如何用责任链默认优雅地进行参数校验
还在自己实现责任链?我建议你造轮子之前先看看这个开源项目
![还在自己实现<b class='flag-5'>责任</b><b class='flag-5'>链</b>?我建议你造轮子之前先看看这个开源项目](https://file1.elecfans.com//web2/M00/07/FF/wKgaombtGFuAFlFjAAMfA3tGG9c190.png)
评论