业务设计——责任链验证推翻 if-else 炼狱

2023-10-28 20:44

本文主要是介绍业务设计——责任链验证推翻 if-else 炼狱,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

责任链模式

1. 什么是责任链模式

        在责任链模式中,多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条,链条上的每个处理器各自承担各自的处理职责。

image.png

2. 责任链模式优点

        责任链模式的优点在于,它可以动态地添加、删除和调整处理者对象,从而灵活地构建处理链。同时,它也避免了请求发送者和接收者之间的紧耦合,增强了系统的灵活性和可扩展性。


业务中理解责任链

购票请求验证

        在实际购票业务场景中,用户发起一次购票请求后,购票接口在真正完成创建订单和扣减余票行为前,需要验证当前请求中的参数是否正常请求,或者说是否满足购票情况。

  1. 购票请求用户传递的参数是否为空,比如:车次 ID、乘车人、出发站点、到达站点等。
  2. 购票请求用户传递的参数是否正确,比如:车次 ID 是否存在、出发和到达站点是否存在等。
  3. 需要购票的车次是否满足乘车人的数量,也就是列车对应座位的余量是否充足。
  4. 乘客是否已购买当前车次,或者乘客是否已购买当天时间冲突的车次。
  5. 可能实际场景中需要验证的还有很多,就不逐一举例了。

对于完成这些前置校验逻辑,示例代码如下:

public TicketPurchaseRespDTO purchaseTickets(PurchaseTicketReqDTO requestParam) {// 购票请求用户传递的参数是否为空// 购票请求用户传递的参数是否正确// 需要购票的车次是否满足乘车人的数量// 乘客是否已购买当前车次,或者乘客是否已购买当天时间冲突的车次// ......
}

        解决前置校验需求需要实现一堆逻辑【if-else炼狱】,常常需要写上几百上千行代码。并且,上面的代码不具备开闭原则,以及代码扩展性,整体来说复杂且臃肿。

        为了避免这种坏代码味道【shi山】,我们可以运用责任链设计模式,对购票验证逻辑进行抽象。把每一个if的判断抽象为一个handler过滤器,多个if就化解成了一个过滤器链(职责链),这样子就可以避免在service业务层写太多校验逻辑,让代码更加清爽!并且如果后续还需要新增说明校验的判断,直接新增一个处理器对象就行,无需改动源代码,符合开闭原则


责任链模式重构

下面我们以一个购买商品的业务来搭建责任链架构

1.定义所有过滤链的抽象接口

        为了方便对责任链流程中的任务进行顺序处理,我们需要继承 Spring 框架中的排序接口 Ordered。这将有助于保证责任链中的处理器的顺序执行

public interface AbstractChainHandler <T> extends Ordered { /*** 在这个方法里面定义过滤器要干的事儿,每个过滤器链实例通过重写该方法来实现响应的处理逻辑* @param requestParam 请求的参数(过滤链要加工校验的对象实际上就是源自于前端传过来的参数)*/void handler(T requestParam);/*** 一个项目可以有多条过滤链,得通过mark来锁定指定的那条链,也就是说mark就是众多处理器的划分参考* @return 一条过滤链组件标识*/String mark();
}

2.定义职责链初始化器和启用函数

这个类中只要做两件事:

  • 项目一启动就把多个过滤器链初始化加载好
  • 准备好调用过滤器链路的总开关函数,我传入一条过滤链的标识mark和请求参数requestParam,你就得给我把这个链路跑起来依次调用我的处理器去校验
public final class AbstractChainContext<T> implements CommandLineRunner { // CommandLineRunner:SpringBoot 启动完成后执行的回调函数run()/*** 初始化好的一条条过滤器链都放到集合容器中存好 Key:过滤器链的标识mark   Value:过滤器链中的处理器集合*/private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();/*** 把这个mask对应的链路跑起来依次调用我的处理器去校验* @param mark         过滤链组件标识* @param requestParam 请求参数*/public void handler(String mark, T requestParam) {// 找到那条链子对应的排好序的处理器集合List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);if (CollectionUtils.isEmpty(abstractChainHandlers)) {throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));}// 按序依次调用处理器来进行校验abstractChainHandlers.forEach(each -> each.handler(requestParam));}/*** 初始化项目中的所有过滤链*/@Overridepublic void run(String... args) throws Exception {// 调用 SpirngIOC 工厂获取 AbstractChainHandler 接口类型的 Bean    Key:Bean的名称  Value:处理器实例Map<String, AbstractChainHandler> chainFilterMap = ApplicationContextHolder.getBeansOfType(AbstractChainHandler.class);// chainFilterMap中堆了项目的所有过滤器,下面根据过滤器链的标识mark来进行划分,把划分的各个链子都丢到集合容器中存好chainFilterMap.forEach((beanName, bean) -> {List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());if (CollectionUtils.isEmpty(abstractChainHandlers)) {abstractChainHandlers = new ArrayList();}abstractChainHandlers.add(bean);// 注意这里通过Order数值来排序好一条过滤链中的过滤器调用的顺序List<AbstractChainHandler> actualAbstractChainHandlers = abstractChainHandlers.stream().sorted(Comparator.comparing(Ordered::getOrder)).collect(Collectors.toList());abstractChainHandlerContainer.put(bean.mark(), actualAbstractChainHandlers);});}

3.定义一条责任链的抽象

在这里,我们定义一个针对购买操作的责任链的抽象,这样子就可以将处理器实例通过接口进行一个划分

public interface PurchaseFilter <T extends 请求参数的封装类> extends AbstractChainHandler<请求参数的封装类> {@Overridedefault String mark() {// 这个name不是自定义的属性,而是每个枚举类型的名称,默认是1开始,类似于数组下标的东西return FilterChainMarkEnum.PURCHASE_FILTER.name();}
}

4.定义责任链中的处理器实例

定义查询该商品库存是否满足购买数量的处理器

@Component
public class QueryHandler implements PurchaseFilter {@Overridepublic void handler(PurchaseTicketReqDTO requestParam) {// 校验该商品库存是否满足购买数量操作,不是就抛异常中止责任链校验操作}@Overridepublic int getOrder() {return 处理器在链路中的order值;}
}

定义查询用户余额是否充足的处理器

@Component
public class HavaMoneyHandler implements PurchaseFilter {@Overridepublic void handler(PurchaseTicketReqDTO requestParam) {// 校验余额是否充足操作,不是就抛异常中止责任链校验操作}@Overridepublic int getOrder() {return 处理器在链路中的order值;}
}

如果还有什么校验逻辑就直接加处理器就行,不需要动原代码,满足开闭原则

5.业务层代码实现

private AbstractChainContext abstractChainContext = new AbstractChainContext();public void purchaseService(T requestParam){try {// 把参数丢进职责链中校验一番先abstractChainContext.handler(FilterChainMarkEnum.PURCHASE_FILTER.name(), requestParam);} catch (Exception e) {// 校验失败,打印结果log.error({"职责链中报错:{}"}, e.getMessage());}// 校验成功啦,正常的CRUD去吧xxxxxxxxx
}

        经过责任链模式的重构,你是否发现业务逻辑变得更加清晰易懂了?采用这种设计模式后,增加或删除相关的业务逻辑变得非常方便,不再需要担心更改上千行代码的几行代码,导致整个业务逻辑受到影响的情况。

文末总结 + 优化思路

本文详细介绍了责任链模式的概念,并通过商品下单场景模拟了真实使用场景。

        为了复用责任链接口定义和上下文,我们通过抽象的方式将责任链门面接口加入到基础组件库中,实现快速接入责任链的目的。

        虽然在本文中,我们没有使用 boolean 类型的返回值,而是通过异常来终止流程,但在后续的增强中,我们可以考虑添加布尔类型的返回值。

        此外,我们还可以在 AbstractChainHandler 中增加是否异步执行的方法,以提高方法执行性能和减少接口响应时间。

        架构设计总是在不断演进,本文的设计也有优化和进步的空间,让我们继续探索责任链模式的更多可能性。

这篇关于业务设计——责任链验证推翻 if-else 炼狱的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/295837

相关文章

Mysql中设计数据表的过程解析

《Mysql中设计数据表的过程解析》数据库约束通过NOTNULL、UNIQUE、DEFAULT、主键和外键等规则保障数据完整性,自动校验数据,减少人工错误,提升数据一致性和业务逻辑严谨性,本文介绍My... 目录1.引言2.NOT NULL——制定某列不可以存储NULL值2.UNIQUE——保证某一列的每一

MySQL 主从复制部署及验证(示例详解)

《MySQL主从复制部署及验证(示例详解)》本文介绍MySQL主从复制部署步骤及学校管理数据库创建脚本,包含表结构设计、示例数据插入和查询语句,用于验证主从同步功能,感兴趣的朋友一起看看吧... 目录mysql 主从复制部署指南部署步骤1.环境准备2. 主服务器配置3. 创建复制用户4. 获取主服务器状态5

Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式

《Java通过驱动包(jar包)连接MySQL数据库的步骤总结及验证方式》本文详细介绍如何使用Java通过JDBC连接MySQL数据库,包括下载驱动、配置Eclipse环境、检测数据库连接等关键步骤,... 目录一、下载驱动包二、放jar包三、检测数据库连接JavaJava 如何使用 JDBC 连接 mys

Spring Security中用户名和密码的验证完整流程

《SpringSecurity中用户名和密码的验证完整流程》本文给大家介绍SpringSecurity中用户名和密码的验证完整流程,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 首先创建了一个UsernamePasswordAuthenticationTChina编程oken对象,这是S

MyBatis设计SQL返回布尔值(Boolean)的常见方法

《MyBatis设计SQL返回布尔值(Boolean)的常见方法》这篇文章主要为大家详细介绍了MyBatis设计SQL返回布尔值(Boolean)的几种常见方法,文中的示例代码讲解详细,感兴趣的小伙伴... 目录方案一:使用COUNT查询存在性(推荐)方案二:条件表达式直接返回布尔方案三:存在性检查(EXI

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、

Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)

《Python爬虫selenium验证之中文识别点选+图片验证码案例(最新推荐)》本文介绍了如何使用Python和Selenium结合ddddocr库实现图片验证码的识别和点击功能,感兴趣的朋友一起看... 目录1.获取图片2.目标识别3.背景坐标识别3.1 ddddocr3.2 打码平台4.坐标点击5.图

Python中的可视化设计与UI界面实现

《Python中的可视化设计与UI界面实现》本文介绍了如何使用Python创建用户界面(UI),包括使用Tkinter、PyQt、Kivy等库进行基本窗口、动态图表和动画效果的实现,通过示例代码,展示... 目录从像素到界面:python带你玩转UI设计示例:使用Tkinter创建一个简单的窗口绘图魔法:用

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica