今天在做业务时遇到一个商家新增优惠卷需要进行大量逻辑判断的场景,一大坨的if-else判断实在看着头疼,想到之前学习过的责任链模式刚好可以解决这个问题,于是重构了这部分逻辑。

/**
 * 优惠券模板业务逻辑实现层
 */
@Service
@RequiredArgsConstructor
public class CouponTemplateServiceImpl extends ServiceImpl<CouponTemplateMapper, CouponTemplateDO> implements CouponTemplateService {

    private final CouponTemplateMapper couponTemplateMapper;
    private final StringRedisTemplate stringRedisTemplate;

    private final int maxStock = 20000000;

    @Override
    public void createCouponTemplate(CouponTemplateSaveReqDTO requestParam) {
        // 验证必填参数是否为空或空的字符串
        if (StrUtil.isEmpty(requestParam.getName())) {
            throw new ClientException("优惠券名称不能为空");
        }

        if (ObjectUtil.isEmpty(requestParam.getSource())) {
            throw new ClientException("优惠券来源不能为空");
        }

       ----------------------------省略复杂判断逻辑

        // 验证参数数据是否正确
        if (ObjectUtil.equal(requestParam.getTarget(), DiscountTargetEnum.PRODUCT_SPECIFIC)) {
            // 调用商品中台验证商品是否存在,如果不存在抛出异常
            // ......
        }

        // 新增优惠券模板信息到数据库
        CouponTemplateDO couponTemplateDO = BeanUtil.toBean(requestParam, CouponTemplateDO.class);
        couponTemplateDO.setStatus(CouponTemplateStatusEnum.ACTIVE.getStatus());
        couponTemplateDO.setShopNumber(UserContext.getShopNumber());
        couponTemplateMapper.insert(couponTemplateDO);

        // 缓存预热:通过将数据库的记录序列化成 JSON 字符串放入 Redis 缓存
        CouponTemplateQueryRespDTO actualRespDTO = BeanUtil.toBean(couponTemplateDO, CouponTemplateQueryRespDTO.class);
        Map<String, Object> cacheTargetMap = BeanUtil.beanToMap(actualRespDTO, false, true);
        Map<String, String> actualCacheTargetMap = cacheTargetMap.entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> entry.getValue() != null ? entry.getValue().toString() : ""
                ));
        String couponTemplateCacheKey = String.format(MerchantAdminRedisConstant.COUPON_TEMPLATE_KEY, couponTemplateDO.getId());
        stringRedisTemplate.opsForHash().putAll(couponTemplateCacheKey, actualCacheTargetMap);
    }
}

使用 责任链 模式优化原有复杂判断逻辑

判断的内容主要分三块:

  1. 参数合规判断,主要对是否为空做判断
  2. 业务合规判断,主要对逻辑正确的判断
  3. 库存合规判断,主要发起远程调用查询库存

以上任意部分都可能在日后新增或修改,所以不能写死在业务逻辑中。


引入责任链模式解决紧耦合的问题:

责任链抽象接口
public interface MerchantAdminAbstractChainHandler<T> extends Ordered {

    /**
     * 执行责任链逻辑
     *
     * @param requestParam 责任链执行入参
     */
    void handler(T requestParam);

    /**
     * @return 责任链组件标识
     */
    String mark();
}

本业务责任链组件标识:MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY

责任链上下文容器

这个容器就是用来存储责任链的实现类,其中的 run 方法是 CommandLineRunner 的实现类,是在服务启动后做数据准备的方法。

/**
 * 商家后管责任链上下文容器
 */
@Component
public final class MerchantAdminChainContext<T> implements ApplicationContextAware, CommandLineRunner {

    /**
     * 应用上下文,我们这里通过 Spring IOC 获取 Bean 实例
     */
    private ApplicationContext applicationContext;
    /**
     * 保存商家后管责任链实现类
     */
    private final Map<String, List<MerchantAdminAbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();

    /**
     * 责任链组件执行
     *
     * @param mark         责任链组件标识
     * @param requestParam 请求参数
     */
    public void handler(String mark, T requestParam) {
        // 根据 mark 标识从责任链容器中获取一组责任链实现 Bean 集合
        List<MerchantAdminAbstractChainHandler> 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 {
        // 从 Spring IOC 容器中获取指定接口 Spring Bean 集合
        Map<String, MerchantAdminAbstractChainHandler> chainFilterMap = applicationContext.getBeansOfType(MerchantAdminAbstractChainHandler.class);
        chainFilterMap.forEach((beanName, bean) -> {
            // 判断 Mark 是否已经存在抽象责任链容器中,如果已经存在直接向集合新增;如果不存在,创建 Mark 和对应的集合
            List<MerchantAdminAbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.getOrDefault(bean.mark(), new ArrayList<>());
            abstractChainHandlers.add(bean);
            abstractChainHandlerContainer.put(bean.mark(), abstractChainHandlers);
        });
        abstractChainHandlerContainer.forEach((mark, unsortedChainHandlers) -> {
            // 对每个 Mark 对应的责任链实现类集合进行排序,优先级小的在前
            unsortedChainHandlers.sort(Comparator.comparing(Ordered::getOrder));
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

handler 方法根据 mark 标识从责任链容器中获取一组责任链实现 Bean 集合。

责任链容器是一张HashMap,键为责任链唯一标识(MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY),值就是对应责任链实现类的集合:List<MerchantAdminAbstractChainHandler>

使用

经过上面的抽象和封装,我们的责任链变得极易使用,在业务类中,我们只需要注入责任链容器,调用 handler 方法,传入参数,按序执行集合内的每个实现类中的 handler 逻辑即可。

merchantAdminChainContext.handler(MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY.name(), requestParam);

这样设计满足 OOP 的开闭原则吗?

我们是通过 Spring IOC 容器去获取责任链处理器的,所以不管新增和删除都不需要变更获取逻辑。新增的话创建对应处理器即可,符合开闭原则。

此作者没有提供个人介绍
最后更新于 2024-08-27