业务设计——责任链验证推翻 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

相关文章

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

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

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

业务协同平台--简介

一、使用场景         1.多个系统统一在业务协同平台定义协同策略,由业务协同平台代替人工完成一系列的单据录入         2.同时业务协同平台将执行任务推送给pda、pad等执行终端,通知各人员、设备进行作业执行         3.作业过程中,可设置完成时间预警、作业节点通知,时刻了解作业进程         4.做完再给你做过程分析,给出优化建议         就问你这一套下

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

easyui同时验证账户格式和ajax是否存在

accountName: {validator: function (value, param) {if (!/^[a-zA-Z][a-zA-Z0-9_]{3,15}$/i.test(value)) {$.fn.validatebox.defaults.rules.accountName.message = '账户名称不合法(字母开头,允许4-16字节,允许字母数字下划线)';return fal

easyui 验证下拉菜单select

validatebox.js中添加以下方法: selectRequired: {validator: function (value) {if (value == "" || value.indexOf('请选择') >= 0 || value.indexOf('全部') >= 0) {return false;}else {return true;}},message: '该下拉框为必选项'}