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

news/2024/5/19 12:55:43 标签: java, 责任链模式

责任链模式

1. 什么是责任链模式

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

image.png

2. 责任链模式优点

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


业务中理解责任链

购票请求验证

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

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

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

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

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

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


责任链模式重构

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

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

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

java">public interface AbstractChainHandler <T> extends Ordered { 
    /**
     * 在这个方法里面定义过滤器要干的事儿,每个过滤器链实例通过重写该方法来实现响应的处理逻辑
     * @param requestParam 请求的参数(过滤链要加工校验的对象实际上就是源自于前端传过来的参数)
     */
    void handler(T requestParam);

    /**
     * 一个项目可以有多条过滤链,得通过mark来锁定指定的那条链,也就是说mark就是众多处理器的划分参考
     * @return 一条过滤链组件标识
     */
    String mark();
}

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

这个类中只要做两件事:

  • 项目一启动就把多个过滤器链初始化加载好
  • 准备好调用过滤器链路的总开关函数,我传入一条过滤链的标识mark和请求参数requestParam,你就得给我把这个链路跑起来依次调用我的处理器去校验
java">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));
    }

    /**
     * 初始化项目中的所有过滤链
     */
    @Override
    public 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.定义一条责任链的抽象

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

java">public interface PurchaseFilter <T extends 请求参数的封装类> extends AbstractChainHandler<请求参数的封装类> {

    @Override
    default String mark() {
        // 这个name不是自定义的属性,而是每个枚举类型的名称,默认是1开始,类似于数组下标的东西
        return FilterChainMarkEnum.PURCHASE_FILTER.name();
    }
}

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

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

java">@Component
public class QueryHandler implements PurchaseFilter {

    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        // 校验该商品库存是否满足购买数量操作,不是就抛异常中止责任链校验操作
    }

    @Override
    public int getOrder() {
        return 处理器在链路中的order值;
    }
}

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

java">@Component
public class HavaMoneyHandler implements PurchaseFilter {

    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        // 校验余额是否充足操作,不是就抛异常中止责任链校验操作
    }

    @Override
    public int getOrder() {
        return 处理器在链路中的order值;
    }
}

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

5.业务层代码实现

java">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 中增加是否异步执行的方法,以提高方法执行性能和减少接口响应时间。

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


http://www.niftyadmin.cn/n/5130767.html

相关文章

网络编程 - IP协议

目录 一&#xff0c;IP协议格式 1.1 拆包组包 1.2 8位生存空间 二&#xff0c;地址管理 2.1 动态分配 IP 2.2 NAT 机制&#xff08;网络地址转换&#xff09; 2.3 IPv6 2.4 网段划分 三&#xff0c;路由选择 一&#xff0c;IP协议格式 4位版本&#xff1a;IPv44位首部长…

pytorch 笔记:index_select

1 基本使用方法 index_select 是 PyTorch 中的一个非常有用的函数&#xff0c;允许从给定的维度中选择指定索引的张量值 torch.index_select(input, dim, index, outNone) -> Tensorinput从中选择数据的源张量dim从中选择数据的维度index 一个 1D 张量&#xff0c;包含你想…

星闪技术 NearLink 一种专门用于短距离数据传输的新型无线通信技术

本心、输入输出、结果 文章目录 星闪技术 NearLink 一种专门用于短距离数据传输的新型无线通信技术前言星闪技术 NearLink 的诞生背景星闪技术 NearLink 简介星闪技术 NearLink 技术是一种蓝牙技术吗星闪技术 NearLink 优势星闪技术 NearLink 应用前景弘扬爱国精神星闪技术 Nea…

iphone备份后怎么转到新手机,iphone备份在哪里查看

iphone备份会备份哪些东西&#xff1f;iphone可根据需要备份设备数据、应用数据、苹果系统等。根据不同的备份数据&#xff0c;可备份的数据类型不同&#xff0c;有些工具可整机备份&#xff0c;有些工具可单项数据备份。本文会详细讲解苹果手机备份可以备份哪些东西。 一、ip…

互联多区域电网的负荷频率控制研究

摘要 电力行业的发展程度是衡量国民经济水平以及国家安全保障的一项重要指标。多区域负荷频率控制系统作为现代电力系统发展的重要趋势&#xff0c;在可靠性、经济性和稳定性上都具备一定的优势。保证系统稳定和输出电能的质量是电网运行的关键。电力系统输出电能质量的优劣取决…

聊聊logback的AsyncAppender

序 本文主要研究一下logback的AsyncAppender AsyncAppender ch/qos/logback/classic/AsyncAppender.java public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {boolean includeCallerData false;/*** Events of level TRACE, DEBUG and INFO are…

手把手教你用C/C++实现多项式操作

引言 多项式操作是计算机科学和数学领域中的一个重要主题。在计算机科学中&#xff0c;多项式操作常用于代数表达式求解、图形绘制和优化算法等众多领域。本篇博客将分析一个用C编写的多项式操作代码&#xff0c;该代码实现了多项式的创建、相加和相乘等基本操作。我们将逐步解…

SDK 资源

目录 资源的使用 带资源的.exe文件的编译方式 向窗口发送消息 菜单 加载菜单 菜单消息 图标 光标 快捷键 字符串 资源的使用 在VS2019中&#xff0c;点击视图下的其他窗口&#xff0c;资源视图&#xff0c;就可以看到本项目的所有资源 鼠标右键添加-资源&#xff0c…