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

    文章

    2997

    浏览量

    115751
  • 代码
    +关注

    关注

    30

    文章

    4945

    浏览量

    73211
  • 过滤器
    +关注

    关注

    1

    文章

    442

    浏览量

    20853

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

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

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    打击:下代网络疑云

    并非简单的好或坏,它更像面镜子,映照出技术背后的权力与责任
    的头像 发表于 12-02 12:47 6176次阅读
    星<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 5226次阅读

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

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

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

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

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

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

    如何将FX3与WSL(Linux 的 Windows 子系统)一起使用?

    如何将 FX3 与 WSL(Linux 的 Windows 子系统)一起使用? 我在 /dev/ 中找不到任何设备 我有许多项目在 Windows 上使用VISUAL STUDIO项目进行操作,因此请验证该设备是否在 Windows 上运行。
    发表于 05-06 07:11

    屏蔽网线可以和电线一起

    屏蔽网线与电线不建议一起走线,原因主要有以下几点: 电磁干扰:电源线在传输电能时会产生电磁场,而屏蔽网线中的导线可能会受到这个电磁场的干扰。这种干扰可能导致屏蔽网线的信号质量下降、速度变慢,甚至无法
    的头像 发表于 03-07 10:47 1568次阅读

    AN-166:与Linduino一起飞行中更新

    电子发烧友网站提供《AN-166:与Linduino一起飞行中更新.pdf》资料免费下载
    发表于 01-12 10:09 0次下载
    AN-166:与Linduino<b class='flag-5'>一起</b>飞行中更新

    ADS5404EVM 和TSW1400EVM一起使用就可以开发吗?

    ADS5404EVM 和TSW1400EVM一起使用就可以开发吗? 这个开发板一起使用的时候还需要购买其他互联电缆不? 开发套件中有没有包含信号和时钟的输入同轴电缆? 三个问题。
    发表于 12-30 08:30

    和Dr Peter一起学KiCad 4.8:设计规则检查(DRC)

    和Dr Peter一起学KiCad 4.8:设计规则检查(DRC)
    的头像 发表于 12-25 14:55 2845次阅读
    和Dr Peter<b class='flag-5'>一起</b>学KiCad 4.8:设计规则检查(DRC)

    快来“一起鸿蒙”!体验更出色,智慧再升级

    当鸿蒙遇上脱口秀,会擦出什么样的火花?“一起鸿蒙”给出了答案。华为邀请了呼兰、贾耗、漫才兄弟、庞博、小鹿等艺人,用脱口秀独有的风趣幽默方式,将原生鸿蒙带入观众的视野。从真实的应用场景出发,脱口秀
    的头像 发表于 12-25 14:45 639次阅读
    快来“<b class='flag-5'>一起</b>鸿蒙<b class='flag-5'>吧</b>”!体验更出色,智慧再升级

    将UCC39002与3个PT4484模块一起使用

    电子发烧友网站提供《将UCC39002与3个PT4484模块一起使用.pdf》资料免费下载
    发表于 12-21 10:23 2次下载
    将UCC39002与3个PT4484模块<b class='flag-5'>一起</b>使用

    afe5803 SPI控制VCAT 8路每路单独控制还是只能一起控制?

    afe5803SPI控制VCAT 8路每路单独控制还是只能一起控制? 如何选择digital VCNTL mode,哪里可以找到digital Vcntl相关?
    发表于 12-20 07:04