0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

责任链设计模式详解

jf_ro2CN3Fa 来源:芋道源码 2023-05-22 15:12 次阅读

什么是责任链

使用场景

反例

初步改造

缺点

责任链改造

责任链工厂改造

结语

最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。

实际上,针对导入功能,我认为模版方法更合适!为此,隔壁团队也拿出我们的案例,进行了集体 code review。

学好设计模式,且不要为了练习,强行使用!让原本 100 行就能实现的功能,写了 3000 行!对错暂且不论,我们先一起看看责任链设计模式吧!

什么是责任链

责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。

48129cfe-f723-11ed-90ce-dac502259ad0.png

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

视频教程:https://doc.iocoder.cn/video/

使用场景

责任链的使用场景还是比较多的:

多条件流程判断:权限控制

ERP 系统流程审批:总经理、人事经理、项目经理

Java 过滤器的底层实现 Filter

如果不使用该设计模式,那么当需求有所改变时,就会使得代码臃肿或者难以维护,例如下面的例子。

反例

假设现在有一个闯关游戏,进入下一关的条件是上一关的分数要高于 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)角色: 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

481c50d2-f723-11ed-90ce-dac502259ad0.png

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();

}
}

责任链工厂改造

对于上面的请求链,我们也可以把这个关系维护到配置文件中或者一个枚举中。我将使用枚举来教会大家怎么动态的配置请求链并且将每个请求者形成一条调用链。

4825da08-f723-11ed-90ce-dac502259ad0.png

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.Entryentry: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{
//根据全限定类名,加载并初始化该类,即会初始化该类的静态段
Classclazz=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编程会失败?

    亲爱的先生,我们使用了2个Vertex 5&amp; spartan 6 FPGA,用于使用菊花配置模式对这些FPGA进行编程。1.我们正在使用xilinx Blaster“平台电缆USB
    发表于 11-07 08:25

    什么是菊花模式 星型模式

    什么是菊花模式 星型模式 菊花模式   菊花
    发表于 12-05 09:00 8575次阅读

    嵌入式系统的知识平台与平台模式详解

    嵌入式系统的知识平台与平台模式详解 知识经济的时代是一个以知识平台为中心的市场经济时代。嵌入式系统领域的产业、科技,已从资本经济时代封闭
    发表于 03-29 15:09 942次阅读

    电子行业供应责任向前迈出一步

    电子行业供应责任向前迈出一步     瑞士蒙特勒2010年3月11日电 /美通社亚洲/ -- Achilles Information Ltd. 与 Global e-Sustainability Initiati
    发表于 03-11 18:19 478次阅读

    基于MSP430单片机低功耗控制与系统工作模式详解

    基于MSP430单片机低功耗控制与系统工作模式详解
    发表于 10-12 15:29 11次下载
    基于MSP430单片机低功耗控制与系统工作<b class='flag-5'>模式</b><b class='flag-5'>详解</b>

    区块技术有哪些特点_区块技术应用_区块技术的工作原理

    本文以区块为中心,主要介绍了区块技术的特点、区块的分类、区块技术应用以及区块技术的工作原理进行
    发表于 12-18 09:10 2.1w次阅读

    半导体芯片行业的运作模式是什么(IDM/Fabless/Foundry模式

    本文首先详解半导体芯片行业的三种运作模式,分别有IDM、Fabless和Foundry模式。其次介绍了半导体芯片及半导体芯片产业重要环节,具体的跟随小编一起来了解一下。
    的头像 发表于 05-31 11:25 32.7w次阅读

    C语言设计模式的程序资料合集

    之模板模式,C语言设计模式之工厂模式,C语言设计模式责任
    发表于 11-16 08:00 5次下载

    HS6621 串口透传 模式 - [详解]

    HS6621串口透传模式详解
    发表于 12-08 18:36 32次下载
    HS6621 串口透传 <b class='flag-5'>模式</b> - [<b class='flag-5'>详解</b>]

    一起看看责任设计模式吧!

    如何解决这个问题,我们可以通过链表将每一关连接起来,形成责任的方式,第一关通过后是第二关,第二关通过后是第三关 ....,这样客户端就不需要进行多重 if 的判断了
    的头像 发表于 07-08 16:25 920次阅读

    什么是责任

    责任模式是行为模式的一种,它将需要触发的Handler组成一条,发送者将请求发给的第一个接
    的头像 发表于 02-16 14:41 1036次阅读

    如何用责任默认优雅地进行参数校验

    那么有什么更好的参数校验的方式呢?本文就推荐一种通过责任设计模式来优雅地实现参数的校验功能,我们通过一个用户注册的例子来讲明白如何实现。
    的头像 发表于 04-06 15:00 500次阅读

    设计模式行为型:责任模式

    将请求发送者和请求接受者解耦,让请求的接受者形成链式操作,所有人都能够接受接受到请求,直到有人处理请求。
    的头像 发表于 06-06 17:33 815次阅读
    设计<b class='flag-5'>模式</b>行为型:<b class='flag-5'>责任</b><b class='flag-5'>链</b><b class='flag-5'>模式</b>

    设计模式责任模式概述

    设计模式是一些被反复使用的、具有普遍性的设计解决方案,它们是在特定情境下对软件设计问题的成功解决方式的总结和归纳。
    的头像 发表于 09-27 09:54 752次阅读
    设计<b class='flag-5'>模式</b>之<b class='flag-5'>责任</b><b class='flag-5'>链</b><b class='flag-5'>模式</b>概述

    还在自己实现责任?我建议你造轮子之前先看看这个开源项目

    1. 前言 设计模式在软件开发中被广泛使用。通过使用设计模式,开发人员可以更加高效地开发出高质量的软件系统,提高代码的可读性、可维护性和可扩展性。 责任
    的头像 发表于 09-20 14:38 418次阅读
    还在自己实现<b class='flag-5'>责任</b><b class='flag-5'>链</b>?我建议你造轮子之前先看看这个开源项目