代码精简之路-责任链模式

news/2024/11/28 12:07:50/文章来源:https://www.cnblogs.com/cy2011/p/18572619

前言

常说c#、java是面向对象的语言,但我们平时都是在用面向过程的思维写代码,实现业务逻辑像记流水账一样,大篇if else的判断;对业务没有抽象提炼、代码没有分层。随着需求变化、功能逐步拓展、业务逻辑逐渐复杂;代码越来越长、if else嵌套越来越多,代码会变成程序员都厌恶的"屎山"。这种代码后期维护成本非常高、牵一发而动全身、改一处逻辑战战兢兢。假如我们完成开发任务交差,后期维护不关自己的事;但是长期做重复的CRUD、记流水账对我们没有好处。虽然项目不是自己的,但是时间是自己的,这样几年过去似乎没有精进变化,长期下去年龄增大会逐渐丧失竞争力。

下面记录今天开发的一个小功能,演示一步一步重构的过程。

需求

  1. 有一个智能识别的api给用户调用。角色有两个:管理员、普通用户。管理员不限次数调用,普通用户每日限用五次。

简单实现,只判断如果是普通用户就检查次数,不满足就返回提示:

if (service.isNormalUser() && service.freeNumUseUp()) {return AjaxResult.error("普通用户免费识别次数已使用完!");
}// todo:调用识别接口
  1. 功能演变:普通用户可充值后升级为VIP用户,VIP用户在有效期内不限次数使用,过期以后降为普通用户。
    增加VIP角色的检查后:
if (service.isVipUser() && service.vipUserExpire()) {return AjaxResult.error("会员已到期!");
}
if (service.isNormalUser() && service.freeNumUseUp()) {return AjaxResult.error("普通用户免费识别次数已使用完!");
}// todo:调用识别接口

以上修改的问题:普通用户充值以后,是增加一个VIP的角色而不是把原普通用户角色更新为VIP角色。此时这个用户有两个角色,那么上面的代码先判断VIP角色是否到期是没问题的,但是下面又判断了是否为普通用户就有问题了,因为他有两个角色呀,VIP未到期时第2个条件也满足了会给出不合理的提示。怎么改,首先想到的是不是检查VIP后就不检查普通用户了?于是修改为:

if (service.isVipUser()) {if (service.vipUserExpire()) {return AjaxResult.error("会员已到期!");}
} else {if (service.isNormalUser() && service.freeNumUseUp()) {return AjaxResult.error("普通用户免费识别次数已使用完!");}
}// todo:调用识别接口

以上仍然有问题,如果是VIP角色就不会检查普通用户角色了,可是按需求VIP到期以后他还具有普通用户角色,可以在每天免费次数内使用。于是再改:

boolean dontPass = service.isVipUser() && service.vipUserExpire();
if (dontPass) {dontPass = service.isNormalUser() && service.freeNumUseUp();if (dontPass) {return AjaxResult.error("普通用户免费识别次数已使用完!");} else {return AjaxResult.error("会员已到期!");}
}// todo:调用识别接口

以上修改可以满足VIP和普通用户的检查了,还差了管理员的判断,还要再嵌套:

boolean dontPass = !service.isAdmin();
if (dontPass) {dontPass = service.isVipUser() && service.vipUserExpire();if (dontPass) {dontPass = service.isNormalUser() && service.freeNumUseUp();if (dontPass) {return AjaxResult.error("普通用户免费识别次数已使用完!");} else {return AjaxResult.error("会员已到期!");}}
}// todo:调用识别接口

终于满足3个角色的检查了,加了3层if判断。以后再出现新的角色怎么办?如果功能交给同事来升级,原来的代码轻易不敢动只能再嵌套。

梳理以上需求,3个角色有任意一个通过就可以了。实际上检查时可以按以下先后顺序逐个过,最后一个不满足才返回提示。

a. 是否有管理员角色,否进入下一级
b. 是否有VIP角色且未到期,否进入下一级
c. 是否有普通用户角色且满足免费次数条件,否进入下一级;如果没有下一级则检查不通过。

重新设计

  1. 审批角色接口,主要两个功能:a. 角色判断(当前用户是否为本角色),b. 是否检查(审批)通过
public interface IAudit {/*** 角色判断:是否为我的责任** @return*/boolean isMyDuty();/*** 是否通过** @return*/boolean auditPass();/*** 检查(审批)意见:不通过时返回空字符串** @return*/String auditMessage();
}
  1. 审批角色抽象类,实现审批角色接口,并且是3个角色实现类的父类,充当审批角色接口和角色实现类的中间过度。作用是判断检查(审批)是否通过,这里不大容易理解,实际3个角色的实现类分别实现接口就可以了,没有这个中间过度也可以的。为什么要加这个中间类?因为最终检查是否通过要调用isMyDuty和auditPass两个方法,这里可以把这两个方法的调用合并为一个方法,其实就是把判断角色和角色的检查条件统一在这个类而不是在3个实现类里去分别写了,为什么?因为3个实现类要写的判断都是完全一样的代码isMyDuty()&&auditPass(),作用就是本来要写3行,现在只写1行。看上去没有必要?因为现在只有3个类呀,如果以后扩展到5个角色,5类那多了。还有,如果是功能修改呢,那就要6个类里分别改了。每改一个类都需要针对这个类单独测试。修改测试花时间多了,这里只有一次修改测试。
public abstract class AbstractAudit implements IAudit {/*** 角色是否检查通过** @return*/public boolean checkPass() {return isMyDuty() && auditPass();}
}
  1. 3个角色的实现类。
  • 管理员:
@Service
public class AdminAudit extends AbstractAudit {@Autowiredprivate IdentifyService identifyService;@Overridepublic boolean isMyDuty() {return identifyService.isAdmin();}@Overridepublic boolean auditPass() {return true;}/*** 管理员是没有限制的,所以没有提示** @return*/@Overridepublic String auditMessage() {return "";}
}
  • VIP用户:
@Service
public class VipUserAudit extends AbstractAudit {@Autowiredprivate IdentifyService identifyService;@Overridepublic boolean isMyDuty() {return identifyService.isVipUser();}@Overridepublic boolean auditPass() {return !identifyService.vipUserExpire();}/*** 这里还需要优化,因为isMyDuty和auditPass可能被调用两次,可以将isMyDuty、auditPass返回值存在临时变量中** @return*/@Overridepublic String auditMessage() {if (!isMyDuty()) {return "不是会员";} else if (!auditPass()) {return "会员过期";}return "";}
}
  • 普通用户:
@Service
public class NormalUserAudit extends AbstractAudit {@Autowiredprivate IdentifyService identifyService;@Overridepublic boolean isMyDuty() {return identifyService.isNormalUser();}@Overridepublic boolean auditPass() {return !identifyService.freeNumUseUp();}@Overridepublic String auditMessage() {return "普通用户免费识别次数已使用完";}
}
  1. 审批责任链类。作用为添加审批人、审批返回结果。
public class AuditChain {private List<AbstractAudit> chain = new ArrayList<>();/*** 添加审批人** @param auditor*/public void add(AbstractAudit auditor) {chain.add(auditor);}/*** 检查/审批** @return*/public Result audit() {Result result = new Result();// 是否检查通过boolean pass = chain.stream().anyMatch(a -> a.checkPass());result.setPass(pass);if (!pass) {String msg = chain.stream().map(c -> c.auditMessage()).filter(m -> Strings.isNotBlank(m)).collect(Collectors.joining(","));result.setMsg(msg);}return result;}@Datapublic class Result {private boolean pass;private String msg;}
}
  1. 实现检查
// 审批责任链中加入3个角色,这里用的Spring Boot开发,3个角色都是容器注入的,其它框架中手动创建实例// 添加审批人角色
auditChain.add(adminAudit);
auditChain.add(vipUserAudit);
auditChain.add(normalUserAudit);// 审批结果
AuditChain.Result auditResult = auditChain.audit();
if (!auditResult.isPass()) {return AjaxResult.error(auditResult.getMsg());
}

总结

最终的实现代码简洁明了,易维护、易扩展升级:

  1. 核心方法只有auditChain.add和auditChain.audit,一眼看去就能明白作用是加入审批人和实现审批。
  2. 如何扩展功能加入其它角色?创建新的角色类并继承AbstractAudit,并加入到责任链中。不需要在原来的if中嵌套了。
  3. 现在的检查是多个角色中有任意一个通过即可,转换到审批场景就是多角色审批,其中一个角色审批通过即可。如果要需求改成多个角色全部审批通过才行呢?其实就是责任人链中or的关系改为and关系。 只需要修改AuditChain类的audit方法,将chain.stream().anyMatch改为chain.stream().allMatch。anyMatch表示任意一个匹配,allMatch表示全部匹配。如果要在改造前的代码中要实现or到and的变化,原有代码几乎要完全重写。

学习交流:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/842797.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vxe-table 使用表格多选数据、复选框多选

在 vxe-table 启用列多选功能,通过参数 column.type = checkbox 设置类型为多选类型就可以了。 官网:https://vxetable.cn<template><div><vxe-grid v-bind="gridOptions"></vxe-grid></div> </template><script> expor…

【QT】使用Qxlsx读取Excel单元格中函数表达式的结果值

【QT】使用Qxlsx读取Excel单元格中函数表达式的结果值 零、起因 是这样的,目前朋友托我写一款模板生成软件,任务是先把他写的程序文件复制一份出来,然后再根据Excel中对应位置的单元格的值,修改程序文件副本中的某些文件。对于读Excel的需求,经过测试,最终选择Qxlsx这款开…

智慧防汛平台在城市生命线安全建设中的应用

随着城市化进程的加快,城市基础设施的复杂性和互联性不断增强,城市生命线的安全管理面临前所未有的挑战。智慧防汛平台作为城市生命线安全建设的重要组成部分,通过现代信息技术提升城市防汛应急管理的智能化水平,保障城市安全。智慧防汛平台的核心功能智慧防汛平台通常集成…

初探RocketMQ架构

目录一、概述二、概览2.1、部署架构图1.生产者(Producer)2.消费者(Consumer)3.代理服务器(Broker Server)4.名字服务(Name Server)2.2 名词解释1.主题(Topic)2.标签(Tag)3.消息(Message)4.拉取式消费(Pull Consumer)5.推动式消费(Push Consumer)6.生产者组(…

库存系统:应用层、领域层、对接层的架构设计

大家好,我是汤师爷~ 大厂对候选人的要求较高,即使是20k薪资的岗位,也期望应聘者能够独立承担工作职责。 对于30-40k薪资的岗位,需要具备独立系统设计和小型架构设计的能力。 技术专家和架构师岗位(30-50k以上)要求应聘者具有带领团队、负责大型系统架构的经验,并且在架构…

分布式锁的实现原理

介绍分布式锁的实现原理。作者:来自 vivo 互联网服务器团队- Xu Yaoming介绍分布式锁的实现原理。 一、分布式锁概述 分布式锁,顾名思义,就是在分布式环境下使用的锁。众所周知,在并发编程中,我们经常需要借助并发控制工具,如 mutex、synchronized 等,来保障线程安全。但…

HyperWorks变形域和控制柄方法

变形域和控制柄方法 使用变形域和控制柄方法进行网格变形时,网格模型被分割成若干个变形子域,位于变形域上的控制柄常常用来控制变形域形状的变化。当控制柄移动时,变形域的形状随之变化,进而影响变形域内部节点位置的分布。变形过程中,网格以一种合乎逻辑的方式变化,即靠…

记录Vue Antd 表格RowSelection刷新列表后缓存问题

起因原来的代码//tsx部分 <BaseTableoptions={tableData.options}columns={tableData.columns}data={tableData.data}/>const selectKeys = ref<string[]>([])// 表格配置const handleRowSelection = {onChange: (selectedRowKeys: string[], selectedRows: IS…

震惊!推荐一款AI驱动的自动化测试神器:TestCraft

在当今快速迭代的软件开发环境中,自动化测试已经成为确保软件质量的重要一环。然而,传统的手动录制和编写测试脚本的方式不仅耗时耗力,还难以跟上敏捷开发的节奏。 本文将为大家介绍一款基于AI技术的自动化测试工具——TestCraft,它凭借其智能化、易用性和高效性,正逐渐成…

信息安全概论复习-2

计算机系统的可靠性和可用性 系统可靠性定义及测量方法硬件的可靠性和完美性软件的可靠性和完美性容错技术和系统,冗余技术冗余类型,4种,硬件软件时间信息容错系统的工作方式 1、自动检查 2、自动切换 3、自动修复 容错系统和部件--系统级容错、部件级容错--就是备用系统、部…

初探RocketMQ消息消费原理(一)

目录一. 消息消费概述二、消费队列负载机制与重平衡1.1 消费队列负载机制与重平衡1.2 并发消费模型1.3 消息消费进度反馈机制 一. 消息消费概述 消息消费以组的模式开展,一个消费组可以包含多个消费者,每个消费组可以订阅多个主题(一般来说不建议),消费组之间有集群模式和…

rust中使用opencv-cuda和yolov

最近公司有个要识别的项目需要计算机识别,于是就找到了opencv来进行,opencv的cuda版本需要自己来进行编译需要去opencv官网下载,我下载的版本是opencv4.10 https://github.com/opencv/opencv/archive/refs/tags/4.10.0.zip 还有需要opencv_contrib-4.10.0和cmake下载 下载之…