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

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

3天内不再提示

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

Android编程精选 来源:CSDN 作者:Java小海 2022-07-08 16:25 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

  • 什么是责任链
  • 场景
    • 反例
    • 初步改造
    • 缺点
    • 责任链改造
    • 责任链工厂改造
  • 聊聊其他

最近,我让团队内一位成员写了一个导入功能。他使用了责任链模式,代码堆的非常多,bug 也多,没有达到我预期的效果。实际上,针对导入功能,我认为模版方法更合适!为此,隔壁团队也拿出我们的案例,进行了集体 code review。

学好设计模式,且不要为了练习,强行使用!让原本100行就能实现的功能,写了 3000 行!

对错暂且不论,我们先一起看看责任链设计模式吧!

什么是责任链

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

3dca85f4-fc56-11ec-ba43-dac502259ad0.png

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

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

场景

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

  • 多条件流程判断:权限控制
  • ERP 系统流程审批:总经理、人事经理、项目经理
  • Java 过滤器 的底层实现 Filter

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

反例

假设现在有一个闯关游戏,进入下一关的条件是上一关的分数要高于xx

  1. 游戏一共 3 个关卡
  2. 进入第二关需要第一关的游戏得分大于等于 80
  3. 进入第三关需要第二关的游戏得分大于等于 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)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
3de1a3a6-fc56-11ec-ba43-dac502259ad0.jpg责任链改造
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.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();
}
}

基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

项目地址:https://github.com/YunaiV/onemall

聊聊其他

设计模式有很多,责任链只是其中的一种,我觉得很有意思,非常值得一学。

设计模式确实是一门艺术,仍需努力呀

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    20

    文章

    3012

    浏览量

    116906
  • 代码
    +关注

    关注

    30

    文章

    4978

    浏览量

    74443
  • 过滤器
    +关注

    关注

    1

    文章

    444

    浏览量

    21055

原文标题:同事写了一个责任链模式,bug无数!

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    如何使用 Python 将 I2C LCD 与 VIsionFive 一起使用?

    我刚刚发布了篇关于将 I2C LCD 与 VisionFive 一起使用的新文章。你可以检查下。 注意:这是基于 libgpiod 库,我已经在使用这个库。您可
    发表于 03-30 06:52

    如何将 VisionFive 2 与业余无线电一起使用?

    将 VisionFive 2 与业余无线电一起使用
    发表于 03-25 07:01

    深圳争妍微受邀参与电子商会“一起益企”活动走进卡儿酷科技 深化产业协同赋能

    电子商会品牌服务“一起益企——走进优秀企业 促进资源对接”系列活动,走进商会会员、国家级制造业单项冠军、国家级专精特新“小巨人”企业——深圳市卡儿酷科技股份有限公司(以下简称“卡儿酷科技”)
    的头像 发表于 03-17 15:26 373次阅读
    深圳争妍微受邀参与电子商会“<b class='flag-5'>一起</b>益企”活动走进卡儿酷科技 深化产业<b class='flag-5'>链</b>协同赋能

    为什么不能将 USB 磁盘与 exFat 一起使用?

    不能将 USB 磁盘与 exFat 一起使用
    发表于 03-04 06:27

    阿里狗16.6,层叠表能打开,但是打开后内容缩在一起,调整间距之后,关闭在打开还是缩在一起,怎么处理呢

    阿里狗16.6,层叠表能打开,但是打开后内容缩在一起,调整间距之后,关闭在打开还是缩在一起,怎么处理呢 试过初始化窗口,重新破解,还有重新加载补丁,都不行
    发表于 02-13 11:01

    回望2025:与162万开发者一起,让AI硬件触手可及

    设备中。今天,我们想和大家一起回顾这年的成长,也分享我们对未来的期待。2025,我们跟开发者一起完成了1、开发者生态持续壮大截至四季度末,涂鸦平台累计注册开发者超
    的头像 发表于 02-12 18:59 425次阅读
    回望2025:与162万开发者<b class='flag-5'>一起</b>,让AI硬件触手可及

    打击:下代网络疑云

    并非简单的好或坏,它更像面镜子,映照出技术背后的权力与责任
    的头像 发表于 12-02 12:47 6837次阅读
    星<b class='flag-5'>链</b>打击:下<b class='flag-5'>一</b>代网络疑云

    N9H20如何将 SPI 闪存与非作系统 BSP 一起使用?

    N9H20如何将 SPI 闪存与非作系统 BSP 一起使用?
    发表于 09-01 08:27

    N9H20如何将非作系统 NVTFAT 与 SPI 闪存一起使用?

    N9H20如何将非作系统 NVTFAT 与 SPI 闪存一起使用?
    发表于 09-01 06:38

    光纤能与电线一起走吗

    光纤与电线在特定条件下可以一起布线,但需严格遵守安全规范和物理隔离要求,以下是详细分析: 、光纤与电线的物理特性差异 光纤 传输介质:以光信号传输数据,不导电,因此不受电磁干扰(EMI
    的头像 发表于 07-14 10:40 1w次阅读

    寻开发伙伴 一起搞细胞电阻仪,有兴趣的朋友来聊聊!

    寻开发伙伴 一起搞细胞电阻仪,有兴趣的朋友来聊聊!
    发表于 07-10 15:51

    是否可以将客户端控件与CYW920706WCDEVAL一起使用?

    是否可以将客户端控件与CYW920706WCDEVAL一起使用? 我想用它来发现蓝牙 BR/EDR,然后将其与其他设备配对。 有客户端控制的下载链接或文档吗? 另外,你有 AIROC Connect 蓝牙应用程序的文档吗?
    发表于 07-04 07:50

    无法将Jlink调试器与CYBT263065EVAL COOLDIM_PRG_BOARD连接在一起怎么解决?

    我无法将 Jlink 调试器与 CYBT263065EVAL COOLDIM_PRG_BOARD连接在一起
    发表于 07-03 06:24

    技术故障率降低20%,关键在于这套IoT软硬件体化方案...

    设备运行不稳定、现场环境恶劣、网络集成困难?一起来看看这个案例怎么破局
    的头像 发表于 07-02 10:48 528次阅读
    技术故障率降低20%,关键在于这套IoT软硬件<b class='flag-5'>一</b>体化方案...

    CyU3PDeviceGpioOverride是否仅与LPP引脚一起使用?

    CyU3PDeviceGpioOverride 是否仅与 LPP 引脚一起使用? 如果使用 CyU3PDeviceGpioOverride,则 io_cfg.gpioSimpleEn 跳过还是反之亦然?
    发表于 05-15 07:33