[设计模式]浅谈责任链模式在多级校验中的应用

开篇

提示:阅读本文可以了解到一下内容

  • 结合具体案例了解责任链模式的使用场景
  • 使用责任链模式实现流程编排以及动态扩展
  • 使用Spring @Resource注解的骚操作
  • 递归设置责任链路


前言

责任链模式,顾名思义,就是将多个对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。请求在链路上传递,链路上的每个对象就是一个处理器,每个处理器都可以对请求进行处理,或者直接传递给下一个处理器处理。


应用场景

责任链模式在实际工作中主要有以下两个应用场景:

  • 多重检验。操作需要经过一系列的校验,通过校验后才执行某些操作
  • 工作流。类似企业的OA系统会制定很多流程,一级一级的去处理任务

下面我们通过一个案例来了解一下责任链模式。

案例-创建商品的多重校验场景

以创建商品为例,假设商品创建逻辑分为以下三步完成:

  1. 创建商品
  2. 校验商品参数
  3. 保存商品

第2步校验商品又分为多种情况的校验,必填字段校验、规格校验、价格校验、库存校验等等。这些检验逻辑像一个流水线,要想创建出一个商品,必须通过这些校验。
伪代码如下:

if(nullCheck(param)) throw Exception
if(specCheck(param)) throw Exception
if(stocCheck(param)) throw Exception
return true;

如上代码看起来非常工整且逻辑清晰,但随着业务需求不断增加,相关的校验逻辑也越来越多,越来越复杂,可能使代码非常臃肿且不易于维护。更糟糕的是这些校验的组件不可复用,当有其它需求需要用到一些校验的时候,系统的维护成本也越来越高。
接下来我们使用责任链模式进行优化:创建商品的每一个校验步骤都可以作为一个单独的校验服务,抽离出一个类,便于复用。这些服务形成一个链式调用,请求在处理链上传递,如果校验不通过,则处理器不再向下传递请求,直接返回错误或者抛出异常;若所有校验都通过,那么则执行最后的业务处理比如保存商品。
在这里插入图片描述

二、案例实战:责任链模式实现创建商品校验

1.UML图

在这里插入图片描述
AbstractCheckHandler 表示处理器抽象类,负责抽象处理器行为。其有3个子类,分别是:

  • NullValueCheckHandler:空值校验处理器
  • PriceCheckHandler:价格校验处理
  • StockCheckHandler:库存校验处理器

AbstractCheckHandler抽象类中, handle()定义了处理器的抽象方法,其子类需要重写handle()方法以实现特殊的处理器校验逻辑;

protected ProductCheckHandlerConfig config 是处理器的动态配置类,使用protected声明,每个子类处理器都持有该对象。该对象用于声明当前处理器、以及当前处理器的下一个处理器nextHandler,另外也可以配置一些特殊属性,比如说接口降级配置、超时时间配置等。

AbstractCheckHandler nextHandler 是当前处理器持有的下一个处理器的引用,当前处理器执行完毕时,便调用nextHandler执行下一处理器的handle()校验方法;

protected Result next() 是抽象类中定义的,执行下一个处理器的方法,使用protected声明,每个子类处理器都持有该对象。当子类处理器执行完毕(通过)时,调用父类的方法执行下一个处理器nextHandler。

HandlerClient 是执行处理器链路的客户端,HandlerClient.executeChain()方法负责发起整个链路调用,并接收处理器链路的返回值。

2.商品参数对象:保存商品的入参

ProductVO是创建商品的参数对象,包含商品的基础信息

/**
 * 商品对象
 */
@Data
@Builder
public class ProductVO {
    /**
     * 商品SKU,唯一
     */
    private Long skuId;
    /**
     * 商品名称
     */
    private String skuName;
    /**
     * 商品图片路径
     */
    private String imgPath;
    /**
     * 价格
     */
    private BigDecimal price;
    /**
     * 库存
     */
    private Integer stock;
}

AbstractCheckHandler :处理器抽象类,并使用@Component注解注册为由Spring管理的Bean对象,这样做的好处是,我们可以轻松的使用Spring来管理这些处理器Bean。

/**
 * 抽象类处理器
 */
@Component
public abstract class AbstractCheckHandler {

    /**
     * 当前处理器持有下一个处理器的引用
     */
    @Getter
    @Setter
    private AbstractCheckHandler nextHandler;


    /**
     * 处理器执行方法
     * @param param
     * @return
     */
    public abstract Result handle(ProductVO param);

    /**
     * 处理器配置
     */
    @Setter
    @Getter
    protected ProductCheckHandlerConfig config;

    /**
     * 链路传递
     * @param param
     * @return
     */
    protected Result next(ProductVO param) {
        //下一个链路没有处理器了,直接返回
        if (Objects.isNull(nextHandler)) {
            return Result.success();
        }

        //执行下一个处理器
        return nextHandler.handle(param);
    }

}

ProductCheckHandlerConfig 配置类:

/**
 * 处理器配置类
 */
@AllArgsConstructor
@Data
public class ProductCheckHandlerConfig {
    /**
     * 处理器Bean名称
     */
    private String handler;
    /**
     * 下一个处理器
     */
    private ProductCheckHandlerConfig next;
    /**
     * 是否降级
     */
    private Boolean down = Boolean.FALSE;
}

AbstractCheckHandler抽象类处理器有3个子类分别是:

  • NullValueCheckHandler:空值校验处理器
  • PriceCheckHandler:价格校验处理
  • StockCheckHandler:库存校验处理器

同样,使用@Component注册为由Spring管理的Bean对象

/**
 * 空值校验处理器
 */
@Component
public class NullValueCheckHandler extends AbstractCheckHandler{

    @Override
    public Result handle(ProductVO param) {
        System.out.println("空值校验 Handler 开始...");

        //降级:如果配置了降级,则跳过此处理器,执行下一个处理器
        if (super.getConfig().getDown()) {
            System.out.println("空值校验 Handler 已降级,跳过空值校验 Handler...");
            return super.next(param);
        }

        //参数必填校验
        if (Objects.isNull(param)) {
            return Result.failure(ErrorCode.PARAM_NULL_ERROR);
        }
        //SkuId商品主键参数必填校验
        if (Objects.isNull(param.getSkuId())) {
            return Result.failure(ErrorCode.PARAM_SKU_NULL_ERROR);
        }
        //Price价格参数必填校验
        if (Objects.isNull(param.getPrice())) {
            return Result.failure(ErrorCode.PARAM_PRICE_NULL_ERROR);
        }
        //Stock库存参数必填校验
        if (Objects.isNull(param.getStock())) {
            return Result.failure(ErrorCode.PARAM_STOCK_NULL_ERROR);
        }

        System.out.println("空值校验 Handler 通过...");

        //执行下一个处理器
        return super.next(param);
    }
}
/**
 * 价格校验处理器
 */
@Component
public class PriceCheckHandler extends AbstractCheckHandler{
    @Override
    public Result handle(ProductVO param) {
        System.out.println("价格校验 Handler 开始...");

        //非法价格校验
        boolean illegalPrice =  param.getPrice().compareTo(BigDecimal.ZERO) <= 0;
        if (illegalPrice) {
            return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
        }
        //其他校验逻辑...

        System.out.println("价格校验 Handler 通过...");

        //执行下一个处理器
        return super.next(param);
    }
}
/**
 * 库存校验处理器
 */
@Component
public class StockCheckHandler extends AbstractCheckHandler{
    @Override
    public Result handle(ProductVO param) {
        System.out.println("库存校验 Handler 开始...");

        //非法库存校验
        boolean illegalStock = param.getStock() < 0;
        if (illegalStock) {
            return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
        }
        //其他校验逻辑..

        System.out.println("库存校验 Handler 通过...");

        //执行下一个处理器
        return super.next(param);
    }
}

HandlerClient客户端类负责发起整个处理器链路的执行,通过executeChain()方法。如果处理器链路返回错误信息,即校验未通过,则整个链路截断(停止),返回相应的错误信息。

/**
 * 责任链模式之客户端
 */
public class HandlerClient {

    /**
     * 执行链路
     * @param handler 处理器
     * @param param 商品参数
     * @return
     */
    public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
        //执行处理器
        Result handlerResult = handler.handle(param);
        if (!handlerResult.isSuccess()) {
            System.out.println("HandlerClient 责任链执行失败返回:" + handlerResult.toString());
            return handlerResult;
        }
        return Result.success();
    }
}

以上,责任链模式相关的类已经创建好了。接下来就可以创建商品了。

@Component
public class ProductService {

    /**
     * 使用Spring注入:所有继承了AbstractCheckHandler抽象类的Spring Bean都会注入进来。
     * Map的Key对应Bean的name,Value是name对应相应的Bean
     */
    @Resource
    private Map<String, AbstractCheckHandler> handlerMap;

    /**
     * 创建商品
     * @return
     */
    public Result createProduct(ProductVO param) {

        //参数校验,使用责任链模式
        Result paramCheckResult = this.paramCheckChain(param);
        if (!paramCheckResult.isSuccess()) {
            return paramCheckResult;
        }

        //创建商品
        return this.saveProduct(param);
    }

    /**
     * 参数校验:责任链模式
     * @param param
     * @return
     */
    private Result paramCheckChain(ProductVO param) {

        //获取处理器配置:通常配置使用统一配置中心存储,支持动态变更
        ProductCheckHandlerConfig handlerConfig = this.getHandlerConfigFile();

        //获取处理器
        AbstractCheckHandler handler = this.getHandler(handlerConfig);

        //责任链:执行处理器链路
        Result executeChainResult = HandlerClient.executeChain(handler, param);
        if (!executeChainResult.isSuccess()) {
            System.out.println("创建商品 失败...");
            return executeChainResult;
        }

        //处理器链路全部成功
        return Result.success();
    }

    /**
     * 获取处理器配置:通常配置使用统一配置中心存储,支持动态变更
     * @return
     */
    private ProductCheckHandlerConfig getHandlerConfigFile() {
        //配置中心存储的配置
        String configJson = "{\"handler\":\"nullValueCheckHandler\",\"down\":false,\"next\":{\"handler\":\"priceCheckHandler\",\"next\":{\"handler\":\"stockCheckHandler\",\"next\":null}}}";
        //转成Config对象
        ProductCheckHandlerConfig handlerConfig = JSON.parseObject(configJson, ProductCheckHandlerConfig.class);
        return handlerConfig;
    }

    /**
     * 获取处理器
     * @param config
     * @return
     */
    private AbstractCheckHandler getHandler (ProductCheckHandlerConfig config) {
        //配置检查:没有配置处理器链路,则不执行校验逻辑
        if (Objects.isNull(config)) {
            return null;
        }
        //配置错误
        String handler = config.getHandler();
        if (StringUtils.isBlank(handler)) {
            return null;
        }
        //配置了不存在的处理器
        AbstractCheckHandler abstractCheckHandler = handlerMap.get(config.getHandler());
        if (Objects.isNull(abstractCheckHandler)) {
            return null;
        }

        //处理器设置配置Config
        abstractCheckHandler.setConfig(config);

        //递归设置链路处理器
        abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));

        return abstractCheckHandler;
    }


    private Result saveProduct(ProductVO param) {
        System.out.println("保存商品 成功...");
        return Result.success(param);
    }
}

3.测试代码执行结果

@SpringBootTest
class DesignDemoChainApplicationTests {
    @Resource
    ProductService productService;

    @Resource
    ApproveService approveService;

    @Test
    void contextLoads() {
        ProductVO param = ProductVO.builder()
                .skuId(123L)
                .skuName("测试商品")
                .imgPath("http://..")
                .price(new BigDecimal(-1))
                .stock(1)
                .build();
        productService.createProduct(param);
    }
    }

在这里插入图片描述


总结

在实际应用中使用责任链模式有以下优点:

  • 降低耦合度,分离请求和处理
  • 扩展性高,可以动态新增和删除处理器
  • 代码复用

在实际应用中使用责任链模式有以下缺点:

  • 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用
  • 可能不容易观察运行时的特征,有碍于除错
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拉霍拉卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值