链式多分支规则树模型的应用
目录
引入
开始调用
初始化
执行流程
欢迎关注我的博客!26届java选手,一起加油💘💦👨🎓😄😂
引入
最近在学习一个项目中的链式多分枝规则树模型的使用,模型如下:
如图所示:
这是一种 链式多分支规则树模型 设计模式,核心是通过功能节点自主决策后续流程执行链路,相比责任链模式,它允许更灵活的分支扩展,每个节点像 “决策者” 一样根据规则选择下一步走向。以下是核心组件拆解:
StrategyMapper、StrategyHandler、AbstractStrategyRouter定义在type层,与业务层隔离,然后将DefaultActivityStrategyFactory、AbstractGroupBuyMarketSupport与各节点定义在业务层进行业务逻辑处理。
- 策略映射器(StrategyMapper)与策略处理器(StrategyHandler):
实现抽象类AbstractStrategyRouter
,前者负责策略映射(如定义不同场景对应规则),后者处理具体策略逻辑(如执行规则计算)。 - 策略路由抽象类(AbstractStrategyRouter):
定义路由规则的抽象框架,规范策略映射、处理的通用逻辑,是整个流程的 “规则模板”。 - 策略工厂(DefaultActivityStrategyFactory):
作为 “对象制造工厂”,负责创建拼团活动相关的策略实例,确保策略对象的统一管理与创建。 - 功能服务支撑类(AbstractGroupBuyMarketSupport):
提供底层通用服务(如数据校验、基础计算),像 “后勤保障”,供上层节点流程调用。 - 节点体系(RootNode、SwitchRoot 等):
- 根节点(RootNode):流程起点,类似 “入口”。
- 开关节点(SwitchRoot):核心决策点,根据条件(如用户类型、活动规则)选择分支(走向其他节点或默认分支)。
- 营销节点(MarketNode):处理营销相关逻辑(如优惠计算)。
- 结尾节点(EndNode):流程终点,标志链路结束。
开始调用
@Resourceprivate DefaultActivityStrategyFactory defaultActivityStrategyFactory;@Overridepublic TrialBalanceEntity indexMarketTrial(MarketProductEntity marketProductEntity) throws Exception {// 获取执行策略StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> strategyHandler = defaultActivityStrategyFactory.strategyHandler();// 受理试算操作return strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext());}
初始化
起始点为DefaultActivityStrategyFactory 策略工厂 ,返回了rootNode给我们,也就是此时的 strategyHandler是rootNode类型的,如下:
@Service
public class DefaultActivityStrategyFactory {private final RootNode rootNode;public DefaultActivityStrategyFactory(RootNode rootNode) {this.rootNode = rootNode;}public StrategyHandler<MarketProductEntity, DynamicContext, TrialBalanceEntity> strategyHandler() {return rootNode;}@Data@Builder@AllArgsConstructor@NoArgsConstructorpublic static class DynamicContext {// 拼团活动营销配置值对象private GroupBuyActivityDiscountVO groupBuyActivityDiscountVO;// 商品信息private SkuVO skuVO;// 折扣金额private BigDecimal deductionPrice;// 支付金额private BigDecimal payPrice;// 活动可见性限制private boolean visible;// 活动private boolean enable;}}
执行流程
// 受理试算操作return strategyHandler.apply(marketProductEntity, new DefaultActivityStrategyFactory.DynamicContext());
rootNode继承自AbstractGroupBuyMarketSupport——功能服务支撑类,AbstractGroupBuyMarketSupport又继承自AbstractMultiThreadStrategyRouter——策略路由抽象类,就会去执行下面这里的apply方法,
public abstract class AbstractMultiThreadStrategyRouter<T, D, R> implements StrategyMapper<T, D, R>, StrategyHandler<T, D, R> {@Getter@Setterprotected StrategyHandler<T, D, R> defaultStrategyHandler = StrategyHandler.DEFAULT;public R router(T requestParameter, D dynamicContext) throws Exception {StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);if(null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);return defaultStrategyHandler.apply(requestParameter, dynamicContext);}@Overridepublic R apply(T requestParameter, D dynamicContext) throws Exception {// 异步加载数据multiThread(requestParameter, dynamicContext);// 业务流程受理return doApply(requestParameter, dynamicContext);}/*** 异步加载数据*/protected abstract void multiThread(T requestParameter, D dynamicContext) throws ExecutionException, InterruptedException, TimeoutException;/*** 业务流程受理*/protected abstract R doApply(T requestParameter, D dynamicContext) throws Exception;}
会先去AbstractGroupBuyMarketSupport看看有没有重写multiThread和doApply方法,
public abstract class AbstractGroupBuyMarketSupport<MarketProductEntity, DynamicContext, TrialBalanceEntity> extends AbstractMultiThreadStrategyRouter<cn.bugstack.domain.activity.model.entity.MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, cn.bugstack.domain.activity.model.entity.TrialBalanceEntity> {protected long timeout = 500;@Resourceprotected IActivityRepository repository;@Overrideprotected void multiThread(cn.bugstack.domain.activity.model.entity.MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {// 缺省的方法}}
当我们的rootNode不想用到多线程加载数据的时候就没有重写这个方法,为空,但是rootNode重写了doApply方法,也就是在这里处理rootNode想要处理的业务,
@Slf4j
@Service
public class RootNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {@Resourceprivate SwitchNode switchNode;@Overrideprotected TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {log.info("商品查询试算服务-RootNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));// 参数判断if (StringUtils.isBlank(requestParameter.getUserId()) || StringUtils.isBlank(requestParameter.getGoodsId()) ||StringUtils.isBlank(requestParameter.getSource()) || StringUtils.isBlank(requestParameter.getChannel())) {throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());}return router(requestParameter, dynamicContext);}@Overridepublic StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {return switchNode;}}
执行完doApply方法,就执行return router()方法,这里带上请求参数和上下文对象,router方法在AbstractMultiThreadStrategyRouter类中,负责流转节点
public R router(T requestParameter, D dynamicContext) throws Exception {StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);if(null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);return defaultStrategyHandler.apply(requestParameter, dynamicContext);}
这里调用的get是StrategyMapper——策略映射器的get方法,因为当前对象是rootNode,如果rootNode实现了get就会回到rootNode的get中
public interface StrategyMapper<T, D, R> {/*** 获取待执行策略** @param requestParameter 入参* @param dynamicContext 上下文* @return 返参* @throws Exception 异常*/StrategyHandler<T, D, R> get(T requestParameter, D dynamicContext) throws Exception;}
回到rootNode,这里重写了get,也就是返回我们需要从rootNode去往的下一个节点
public class RootNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {@Resourceprivate SwitchNode switchNode;@Overrideprotected TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {log.info("拼团商品查询试算服务-RootNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));// 参数判断if (StringUtils.isBlank(requestParameter.getUserId()) || StringUtils.isBlank(requestParameter.getGoodsId()) ||StringUtils.isBlank(requestParameter.getSource()) || StringUtils.isBlank(requestParameter.getChannel())) {throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());}return router(requestParameter, dynamicContext);}@Overridepublic StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {return switchNode;}}
这里会返回switchNode给AbstractMultiThreadStrategyRouter,此时会执行switchNode的apply方法。
public R router(T requestParameter, D dynamicContext) throws Exception {StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);if(null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);return defaultStrategyHandler.apply(requestParameter, dynamicContext);}
与上述过程一样,如果switchNode实现了apply中的方法,就会执行,如果没有实现,就不会执行。再次执行doApply后就会再执行router,然后执行switch的get,这里返回了market Node,就会继续往下,以此类推的执行下去,
@Slf4j
@Service
public class SwitchNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {//业务逻辑return router(requestParameter, dynamicContext);}@Overridepublic StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {return marketNode;}}
MarketNode,在这里我们重写MultiThread方法,使用FutureTask异步查询数据后放入上下文,然后在DoApplay中还可以获取到上下文的数据进行业务处理,处理完毕后在此处还能按照业务进入下一个节点或者返回错误节点。
@Slf4j
@Service
public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {@Resourceprivate ErrorNode errorNode;@Resourceprivate TagNode tagNode;@Overrideprotected void multiThread(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {// 异步查询活动配置// 异步查询商品信息 - 在实际生产中,商品有同步库或者调用接口查询。这里暂时使用DB方式查询。// 写入上下文 - 对于一些复杂场景,获取数据的操作,有时候会在下N个节点获取,这样前置查询数据,可以提高接口响应效率}@Overridepublic TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {// 获取上面查询得到数据的上下文数据// 执行业务,继续放入上下文return router(requestParameter, dynamicContext);}@Overridepublic StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {//走异常节点if (null == dynamicContext.getGroupBuyActivityDiscountVO() || null == dynamicContext.getSkuVO() || null == dynamicContext.getDeductionPrice()) {return errorNode;}return tagNode;}}