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
    +关注

    关注

    19

    文章

    2942

    浏览量

    104007
  • 代码
    +关注

    关注

    30

    文章

    4659

    浏览量

    67713
  • 过滤器
    +关注

    关注

    1

    文章

    418

    浏览量

    19353

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

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

收藏 人收藏

    评论

    相关推荐

    选2088还是3051?一起来说说TA们的不同~

    作为工业实践中最常用的现场仪表,变送器被广泛应用于各种工业自控环境,涉及水利水电、铁路交通、智能建筑、生产自控、航空航天、石化、油井、电力、船舶、机床、管道等众多行业。今天我们一起来看看「2088压力变送器」、「3051差压变送器」这两款变送器有啥区别?
    的头像 发表于 09-02 10:40 182次阅读
    选2088还是3051?<b class='flag-5'>一起</b>来说说TA们的不同~

    增量式编码器3大特点,工作模式,精度,输出脉冲信号 一起了解下吗

    增量式编码器3大特点,工作模式,精度,输出脉冲信号...一起了解下吗?在现代工业自动化和控制系统中,增量式编码器扮演着至关重要的角色。它可以将机械运动转化为电信号,为各类设备提供精确的位置和速度
    的头像 发表于 08-15 14:20 171次阅读
    增量式编码器3大特点,工作<b class='flag-5'>模式</b>,精度,输出脉冲信号 <b class='flag-5'>一起</b>了解<b class='flag-5'>一</b>下吗

    DAC8771RGZ电流输出端IOUT和电压输VOUT出端是连在一起的,是否可以不并在一起

    请教下DAC8771RGZ这款芯片,看官方demo板,电流输出端IOUT和电压输VOUT出端是连在一起的,是否可以不并在一起,分成两路,单独分别输出电流或电压吗?
    发表于 08-08 07:59

    普通门电路的输出端能否连在一起

    普通门电路的输出端能否连在一起,取决于具体的应用场景和需求。普通门电路的输出端能否连在一起个复杂的问题,涉及到数字电路设计、逻辑电路分析、信号完整性、电源管理等多个方面。 门电路的基本概念 在
    的头像 发表于 07-30 15:13 251次阅读

    可以将USB主机与Esp8266一起使用吗?

    我可以将 USB 主机(USB A 型母头)与 Esp8266 一起使用吗? 为什么我不能使用它
    发表于 07-19 06:49

    如何将atoi与esp8266 sdk一起使用?

    有谁知道如何将 atoi 与 esp8266 sdk 一起使用?我似乎找不到可以提供它的头文件。 I\'m using \"ESP8266_NONOS_SDK_V1.5.4_16_05_20\"
    发表于 07-09 07:59

    两个STM32的IO口连接到一起,其中个IO口被烧坏的原因?

    如题:两个STM32的IO口连接到一起个单片机IO设置为输出模式(发送数据),另个设置为外部中断模式(接收数据);这样连到
    发表于 04-24 07:53

    六类网线可以和强电一起走吗

    六类网线理论上不建议和强电一起走。从布线规范的角度来看,弱电线路和强电线路通常不建议共用同桥架,以避免潜在的电磁干扰。然而,多年的施工经验表明,在某些情况下,强电线和弱电网线可能一起
    的头像 发表于 04-19 09:55 3591次阅读

    #新开端、新起点,2024一起加油#

    \"新开端、新起点,2024一起加油\" 这句话充满了积极向上的精神和对未来的期待。新开端和新起点意味着我们有机会摒弃过去的不足,以个全新的姿态开始新的旅程。而\"
    发表于 02-26 21:01

    模式带宽在光纤测试中的用途

    大家好,本期我们一起讨论下在光纤测试中经常会听到的两个名词——模式带宽和网络应用,看看他们在光纤测试中的用途及对我们实际应用的光纤路有什么影响?
    的头像 发表于 01-18 10:32 493次阅读
    <b class='flag-5'>模式</b>带宽在光纤测试中的用途

    AD7606的AGND和VXGND是否定要接在一起

    你好: 想咨询下,我们正使用贵公司AD7606作为模拟输入转换芯片,在使用过程中遇到了个问题,因为芯片的AGND和VXGND分开设计没有共接在一起,这样VXGND和VIN就可以实现正负电压采集
    发表于 12-14 07:36

    下载汽车电力系统设计“秘籍”领好礼,一起来为“小新”解惑

    ,貌似在答疑之余,他还带来了套新的“ 干货秘籍 ”,一起来看看吧。 ps:文末有秘籍下载,同时还有好礼相送,记得滑到最后哦~ 森博森博,都说主驱逆变器是电动汽车的“心脏”,那这么重要的“心脏”器件在设计时应重点考虑哪些因素呢? 十分贴切的比喻
    的头像 发表于 11-16 19:10 273次阅读
    下载汽车电力系统设计“秘籍”领好礼,<b class='flag-5'>一起</b>来为“小新”解惑<b class='flag-5'>吧</b>

    单片机的蜂鸣器是否可以与小灯一起使用?

    单片机的蜂鸣器是否可以与小灯一起使用
    发表于 10-31 06:40

    不同品牌的电容器可以一起使用吗?

    电容器作为电子设备中不可或缺的元件之,扮演着储存电荷和调节电流的重要角色。然而,在面对市面上众多品牌的电容器时,我们可能会产生个疑问:不同品牌的电容器能否一起使用?
    的头像 发表于 10-09 15:14 1925次阅读

    设计模式责任模式概述

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