责任链与规则树设计实战解析

前言:我在自学的过程中,了解到责任链和规则树的设计,由于完全不会这些东西,即使写了代码,也看得我一脸懵逼,不清楚代码是怎么跑的,节点的流转是怎么实现的?所以就根据现有的代码和资料进行分析,然后就有了这篇文章。希望这篇文章在帮助我梳理思路的同时能给到你帮助!

如果你需要更全地了解责任链与规则树设计的内容,可以看看下面这位博主的文章:

小傅哥-干掉if...else,最好用的3种设计模式!https://bugstack.cn/md/develop/design-pattern/2024-08-25-chain-tree.html

一,案例背景

案例的背景是一个拼团营销系统,本次任务的主要业务是需要在流程中查询 拼团活动配置 和 拼团商品信息,为了使后续的代码更便于维护扩展,于是添加一个 tree规则树抽象模型,通过解耦逻辑和划分功能区,让代码具有了文档属性,看到对应的类和类下的方法区,就可以轻松的理解代码实现方式。

二,规则树-代码控制 模型设计

这里将结合案例来进行简单的讲解,简略的模型设计分析如下图:

1. 核心角色:“找节点” 和 “做事情”

  • StrategyMapper(策略映射器):负责 “找下一个该执行的节点”,通过 get() 方法完成。

  • StrategyHandler(策略处理器):负责 “执行当前节点的业务逻辑”,通过 apply() 方法完成。

2. 路由模板:AbstractStrategyRouter(抽象策略路由处理器)

它是 “找节点 + 做事情 + 传递流程” 的通用模板,也是节点链的“中转站”。

  • 实现了 StrategyMapper 和 StrategyHandler(所以既会 “找节点”,也会 “做事情”)。
  • 额外有 router() 方法:负责 “把流程传递给下层节点”(比如当前节点执行完,调用下一个节点的业务)。

3. 业务专属扩展:AbstractGroupBuyMarketSupport(抽象拼团营销支撑类)

继承自 AbstractStrategyRouter,所以带有 “找节点、做事情、传流程” 的能力,但专门为 “拼团业务” 定制:

  • 内部带有 “拼团业务需要的配置”(比如设置线程池配置,注入仓储依赖等)。

4. 流程起点工厂:DefaultActivityStrategyFactory(默认活动策略工厂)

负责 “生产流程的起点(根节点)”,通过 strategyHandler() 方法获取 RootNode

5. 流程节点链(责任链模式)

这些节点AbstractStrategyRouterrouter()方法控制节点的流转,每个节点都继承自 AbstractGroupBuyMarketSupport,都有 apply()(做自己的事)和 get()(找下一个节点)方法:

  • RootNode(根节点):流程入口与参数校验。

  • SwitchNode(开关节点):分支判断与流量管控。。

  • MarketNode(营销节点):核心营销逻辑与异步数据聚合。

  • EndNode(结束节点):结果封装与流程收尾。

三,本案例业务的流程

在案例中,前面的节点已经实现了对数据的校验,而此次的主要内容就是在营销节点里查询“活动配置”和“商品信息”。我们在营销节点的 apply()方法 中实现上面提到的业务内容,

1,业务流程走向分析:

我们在测试类中编写一个测试方法去调用活动业务的方法(这里是indexMarketTrial),在业务方法中去调用 DefaultActivityStrategyFactor 类获取到 Root节点通过获取到的 Root节点 调用 apply() 方法进入节点链,在节点链中由 AbstractStrategyRouter 的 router() 方法进行节点的流转控制,router() 方法中调用策略映射器 get() 方法获取下一个节点,得到下一个节点后调用下一个节点的 apply() 方法去执行业务,然后返回。(由于继承关系,每个节点要重写策略处理器的apply方法)。

 拼团活动业务中的service实现类参考:

@Service
public class IIndexGroupBuyMarketServiceImpl implements IIndexGroupBuyMarketService {

    @Resource
    private DefaultActivityStrategyFactory defaultActivityStrategyFactory;

    @Override
    public TrialBalanceEntity indexMarketTrial(MarketProductEntity marketProductEntity) throws Exception {
        StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> strategyHandler = defaultActivityStrategyFactory.strategyHandler();
        TrialBalanceEntity trialBalanceEntity = strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext());
        return trialBalanceEntity;
    }

}

2,问题为什么每次调用 get() 方法就能获取到下一个需要的节点呢?(How节点流转)

补充DefaultActivityStrategyFactory 类中注入了根节点RootNode,且提供了strategyHandler() 方法 返回RootNode。比如在上面的拼团活动service实现类中,会先调用 DefaultActivityStrategyFactory strategyHandler() 方法来获取一个 StrategyHandler 的对象,再通过这个对象 strategyHandler.apply() 去执行自己的业务。(因为这里的strategyHandler已经被赋值为RootNode,所以可以看作RootNode.apply()

后续进入AbstractStrategyRouter 中的 router() 方法调用 get() 能获取到 需要的下一个节点是因为每一个节点都重写了 get() 方法,在节点get()方法中指定了自己的下一个节点是谁。

节点流转展示图
节点流转展示

下面是 AbstractSrategyRouter RootNode 的代码参考

AbstractSrategyRouter.class

public abstract class AbstractStrategyRouter<T, D, R> implements StrategyHandler<T, D, R>, StrategyMapper<T, D, R> {

    @Getter
    @Setter
    protected StrategyHandler<T,D,R> defaultStrategyHandler = StrategyHandler.DEFAULT;

    public R router(T requestParameter, D dynamicContext) throws Exception {
        //通过调用策略映射器get方法,控制节点流程的走向。
        StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);
        
        if (null != strategyHandler) {
            //使用获取到的节点调用apply方法执行业务
            return strategyHandler.apply(requestParameter, dynamicContext);
        }
        //如果获取到了null,则调用"默认策略处理器"的apply()方法(即直接return null)
        return defaultStrategyHandler.apply(requestParameter, dynamicContext);
    }

}


RootNode.class:

@Service
public class RootNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {
    //*MarketProductEntity-入参类型;DefaultActivityStrategyFactory.DynamicContext-动态上下文,TrialBalanceEntity-出参类型
    @Resource
    private SwitchRoot switchRoot;

    @Override
    public TrialBalanceEntity apply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {
        // 参数判断
        if (StringUtils.isBlank(requestParameter.getUserId()) || 其他判断...) {
            throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());
        }

        return router(requestParameter, dynamicContext);
    }
    //重写StrategyMapper的get方法
    @Override
    public StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) {
        return switchRoot;
    }
}

其他节点和RootNode节点一样都是重写 apply() 和 get(),区别只是方法内实现的内容不同。

四、结尾

目前还在学习,先做个阶段性总结帮助自己理解,后续在学习中可能会继续补充修正!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值