责任链模式应用

牢记于心

职责单一: 责任链模式可以将每个验证逻辑封装到一个独立的处理器中,每个处理器负责单一的验证职责,符合单一职责原则。

可扩展性: 增加新的验证逻辑时,只需添加新的处理器,而不需要修改现有的代码。

清晰的流程: 将所有验证逻辑组织在一起,使得代码结构更加清晰,易于理解。

使用场景

现在有个逻辑判断,商家端创建优惠券的时候,需要给出一些指定信息,后台会对这些信息进行判断,比如:优惠券名称是否合法,是否为空,时间是否正确(失效时间先于启用时间),绑定的商品是否存在....等等逻辑判断。

我们不可能说,每加一个判断标准就去核心代码里面改动,不便于维护....

这里采用责任链设计模式,将逻辑判断分为两块,即两个独立的处理器:

判断必要条件是否为空、判断指定商品是否存在(微服务中,往往需要给商品服务发http请求)。

步骤

1、编写责任链接口

java 复制代码
public interface MerchantAdminAbstractChainHandler<T> extends Ordered {

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

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

细节:通过继承Ordered抽象类,让处理器有顺序的执行,先执行性能花费小的,再执行性能花费大的(调用第三方API)。

2、编写责任链处理器实现类

相同的责任链下的不同处理器,他们的mark方法的返回值要一样,后期根据这个做map的key,将同一个责任链进行绑定(value为一个list集合)

java 复制代码
@Component
public class CouponTemplateCreateParamNotNullChainFilter implements MerchantAdminAbstractChainHandler<CouponTemplateSaveReqDTO> {

    @Override
    public void handler(CouponTemplateSaveReqDTO requestParam) {
        if (StrUtil.isEmpty(requestParam.getName())) {
            throw new ClientException("优惠券名称不能为空");
        }

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

        if (ObjectUtil.isEmpty(requestParam.getTarget())) {
            throw new ClientException("优惠对象不能为空");
        }

        if (ObjectUtil.isEmpty(requestParam.getType())) {
            throw new ClientException("优惠类型不能为空");
        }

        if (ObjectUtil.isEmpty(requestParam.getValidStartTime())) {
            throw new ClientException("有效期开始时间不能为空");
        }

        if (ObjectUtil.isEmpty(requestParam.getValidEndTime())) {
            throw new ClientException("有效期结束时间不能为空");
        }

        if (ObjectUtil.isEmpty(requestParam.getStock())) {
            throw new ClientException("库存不能为空");
        }

        if (StrUtil.isEmpty(requestParam.getReceiveRule())) {
            throw new ClientException("领取规则不能为空");
        }

        if (StrUtil.isEmpty(requestParam.getConsumeRule())) {
            throw new ClientException("消耗规则不能为空");
        }
    }

    @Override
    public String mark() {
        return MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY.name();
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

负责调用第三方服务,查看指定商品是否存在(第三方服务往往开设一个接口,给外部调用)

java 复制代码
@Component
public class CouponTemplateCreateParamVerifyChainFilter implements MerchantAdminAbstractChainHandler<CouponTemplateSaveReqDTO> {

    @Override
    public void handler(CouponTemplateSaveReqDTO requestParam) {
        if (ObjectUtil.equal(requestParam.getTarget(), DiscountTargetEnum.PRODUCT_SPECIFIC)) {
            // 调用商品中台验证商品是否存在,如果不存在抛出异常
            // ......
        }
    }

    @Override
    public String mark() {
        return MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY.name();
    }

    @Override
    public int getOrder() {
        return 20;
    }
}

3、责任链处理器上下文

java 复制代码
@Component
public final class MerchantAdminChainContext<T> implements ApplicationContextAware, CommandLineRunner {

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

 
    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;
    }
}

细节:通过ApplicationAware接口和CommandLineRunner接口,进行时期分离,保证Bean对象初始化后,先进行初始化applicationContext对象,等SpringBoot服务完全跑起来后,再去调用重写的run方法(此时applicationContext对象已经完成了初始化)

4、实现类中的使用

java 复制代码
@Service
@RequiredArgsConstructor
public class CouponTemplateServiceImpl extends ServiceImpl<CouponTemplateMapper, CouponTemplateDO> implements CouponTemplateService {

    private final MerchantAdminChainContext merchantAdminChainContext;

    @Override
    public void createCouponTemplate(CouponTemplateSaveReqDTO requestParam) {
        // 通过责任链验证请求参数是否正确
        merchantAdminChainContext.handler(MERCHANT_ADMIN_CREATE_COUPON_TEMPLATE_KEY.name(), requestParam);
    }
}

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

我们直接通过上下文去判断是否符合逻辑,不符合的直接抛错,不再继续...

相关推荐
玉面小君2 天前
C# 设计模式(行为型模式):责任链模式
设计模式·c#·责任链模式
Pee Wee2 天前
责任链模式
java·前端·责任链模式
暮雨c11 天前
重学设计模式-责任链模式
java·设计模式·责任链模式
每天写点bug11 天前
【go每日一题】 责任链模式的实现
开发语言·golang·责任链模式
ADRU12 天前
设计模式-责任链模式
java·设计模式·责任链模式
爱码少年14 天前
springboot中责任链模式之简单应用
spring boot·责任链模式
Funny-Boy17 天前
Chain Responsibility Pattern
责任链模式
liufeismart20241 个月前
Android 第三方框架:RxJava:源码分析:责任链模式
责任链模式·rxjava
岳轩子1 个月前
23种设计模式之责任链模式
java·设计模式·责任链模式