跳转至

2 2~2 3 规则树设计模式模板

在一个工程中,随着不断地承接业务需求逻辑的实现,会有很多复杂场景需要解决。这个时候就会引入设计模式进行解耦和实现,提高工程代码的扩展性。

随着开发的场景越来越多,在各个 service 实现中会存在相同的设计模式。如果是不同的人开发,那么一个责任链,一个规则树,也会有非常多的实现方式。那么这样就会导致后面在进入开发的人,对已存在的代码,维护的成本就越来越高了。  
所以,我们可以进行设计模式抽象模板的通用结构定义,添加一个规则树抽象模型,引入到工程中进行使用。这样后续工程中就可以不断的定义通用的设计模式被不同的场景统一使用了。

模型设计

这是一种链式的多分支规则树模型结构,由功能节点自行决定后续流程的执行链路。这种设计比责任链的扩展性更好,自由度更高。

  • 首先,定义抽象的通用的规则树模型结构:
    • StrategyMapper - 策略映射器
    • StrategyHandler - 策略处理器
    • AbstractStrategyRouter<T, D, R> - 策略路由抽象类
    • 通过泛型设计允许使用方可以自定义出入参和动态上下文,让抽象模板模型具有通用性
  • 然后由调用方自定义出工厂、功能抽象类和一些流程流转的节点。这些节点可以自由组装进行流转,相比于责任链,它的实现方式更具有灵活性

代码实现

types/designs/framework/tree

StrategyMapper 策略映射器

负责根据参数选择策略。

Java
public interface StrategyMapper<T, D, R> {  

    /**  
    * 获取待执行策略  
    *  
    * @param requestParameter 入参  
    * @param dynamicContext 上下文  
    * @return 返参  
    */  
    StrategyHandler<T, D, R> get(T requestParameter, D dynamicContext);  

}
  • StrategyMapper.get 用于获取每一个要执行的节点,相当于从当前流程进入下一个流程的过程。
  • 泛型 T 入参,D 动态上下文,R 返参

StrategyHandler 策略处理器

负责具体执行策略逻辑

Java
1
2
3
4
5
6
7
public interface StrategyHandler<T, D, R> {  

    StrategyHandler DEFAULT = (T, D) -> null;  

    R apply(T requestParameter, D dynamicContext) throws Exception;  

}
  • StrategyHandler.apply 受理执行的业务流程,每个业务流程执行时,如果有数据是从前面节点到后面节点要使用的,可以将其填充到 dynamicContext 动态上下文中。

AbstractStrategyRouter 策略路由器

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

    @Getter  
    @Setter  
    protected 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);  
    }  

}
  • 通过调用策略映射器的 get 方法,控制节点流程的走向。

AbstractMultiThreadStrategyRouter 异步资源加载策略

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

    @Getter  
    @Setter  
    protected 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);  
    }  

    @Override  
    public R apply(T requestParameter, D dynamicContext) throws Exception {  
        // 异步加载数据  
        multiThread(requestParameter, dynamicContext);  
        // 业务流程受理  
        return doApply(requestParameter, dynamicContext);  
    }  

    /**  
    * 多线程处理,异步加载数据  
    *  
    * @param requestParameter  
    * @param dynamicContext  
    */  
    protected abstract void multiThread(T requestParameter, D dynamicContext) throws Exception;  

    /**  
    *  
    * @param requestParameter  
    * @param dynamicContext  
    * @return  
    */  
    protected abstract R doApply(T requestParameter, D dynamicContext) throws Exception;  
}

domain/activity/service

IIndexGroupBuyMarketService 试算服务类接口

Java
1
2
3
public interface IIndexGroupBuyMarketService {  
    TrialBalanceEntity indexMarketTrial(MarketProductEntity marketProductEntity) throws Exception;  
}

IndexGroupBuyMarketService 试算服务类

通过策略模式实现灵活的业务逻辑。

Java
@Slf4j  
@Service  
public class IndexGroupBuyMarketService 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;  
    }  
}

DefaultActivityStrategyFactory 拼团活动策略工厂

策略工厂类,通过返回 RootNode 示例来提供具体的执行逻辑。

Java
@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  
    @NoArgsConstructor  
    @AllArgsConstructor  
    public static class DynamicContext {  

        // 拼团活动营销配置值对象  
        private GroupBuyActivityDiscountVO groupBuyActivityDiscountVO;  
        // 商品sku值对象  
        private SkuVO skuVO;  

    }  

}

AbstractGroupBuyMarketSupport 拼团活动服务支撑类

Java
public abstract class AbstractGroupBuyMarketSupport<MarketProductEntity, DynamicContext, TrialBalanceEntity>  
extends AbstractMultiThreadStrategyRouter<MarketProductEntity, DynamicContext, TrialBalanceEntity> {  

    protected long timeout = 500;  

    @Resource  
    protected IActivityRepository repository;  

    @Override  
    protected void multiThread(MarketProductEntity requestParameter, DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {  
        // 缺省的方法  
    }  
}

RootNode 根节点

Java
@Slf4j  
@Service  
public class RootNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {  

    @Resource  
    private SwitchNode switchNode;  

    @Override  
    public TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {  
        log.info("拼团商品查询试算服务-RootNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));  
        if (StringUtils.isAnyBlank(requestParameter.getUserId(), requestParameter.getGoodsId(), requestParameter.getSource(), requestParameter.getChannel())) {  
            throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());  
        }  
        return router(requestParameter, dynamicContext);  
    }  

    @Override  
    public StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) {  
        return switchNode;  
    }  
}

SwitchNode 开关节点

Java
@Slf4j  
@Service  
public class SwitchNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {  

    @Resource  
    private MarketNode marketNode;  

    @Override  
    public TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {  
        return router(requestParameter, dynamicContext);  
    }  

    @Override  
    public StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) {  
        return marketNode;  
    }  
}

MarketNode 拼团试算节点

Java
@Slf4j  
@Service  
public class MarketNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {  

    @Resource  
    private ThreadPoolExecutor threadPoolExecutor;  

    @Resource  
    private EndNode endNode;  

    @Override  
    protected void multiThread(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws ExecutionException, InterruptedException, TimeoutException {  
        QueryGroupBuyActivityDiscountVOThreadTask queryGroupBuyActivityDiscountVOThreadTask = new QueryGroupBuyActivityDiscountVOThreadTask(repository, requestParameter.getSource(), requestParameter.getChannel());  
        FutureTask<GroupBuyActivityDiscountVO> groupBuyActivityDiscountVOFutureTask = new FutureTask<>(queryGroupBuyActivityDiscountVOThreadTask);  
        threadPoolExecutor.execute(groupBuyActivityDiscountVOFutureTask);  

        // 异步查询商品信息 - 在实际生产中,商品有同步库或者调用接口查询。这里暂时使用DB方式查询。  
        QuerySkuVOFromDBThreadTask querySkuVOFromDBThreadTask = new QuerySkuVOFromDBThreadTask(repository, requestParameter.getGoodsId());  
        FutureTask<SkuVO> skuVOFutureTask = new FutureTask<>(querySkuVOFromDBThreadTask);  
        threadPoolExecutor.execute(skuVOFutureTask);  

        // 写入上下文 - 对于一些复杂场景,获取数据的操作,有时候会在下N个节点获取,这样前置查询数据,可以提高接口响应效率  
        dynamicContext.setGroupBuyActivityDiscountVO(groupBuyActivityDiscountVOFutureTask.get(timeout, TimeUnit.SECONDS));  
        dynamicContext.setSkuVO(skuVOFutureTask.get(timeout, TimeUnit.SECONDS));  
        log.info("拼团商品查询试算服务-MarketNode userId:{} 异步线程加载数据「GroupBuyActivityDiscountVO、SkuVO」完成", requestParameter.getUserId());  
    }  

    @Override  
    protected TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {  
        log.info("拼团商品查询试算服务-MarketNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));  
        return router(requestParameter, dynamicContext);  
    }  

    @Override  
    public StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) {  
        return endNode;  
    }  
}

EndNode 结尾节点

Java
@Slf4j  
@Service  
public class EndNode extends AbstractGroupBuyMarketSupport<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> {  

    @Override  
    public TrialBalanceEntity doApply(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) throws Exception {  
        log.info("拼团商品查询试算服务-EndNode userId:{} requestParameter:{}", requestParameter.getUserId(), JSON.toJSONString(requestParameter));  

        GroupBuyActivityDiscountVO groupBuyActivityDiscountVO = dynamicContext.getGroupBuyActivityDiscountVO();  
        SkuVO skuVO = dynamicContext.getSkuVO();  

        // 返回空结果  
        return TrialBalanceEntity.builder()  
                .goodsId(skuVO.getGoodsId())  
                .goodsName(skuVO.getGoodsName())  
                .originalPrice(skuVO.getOriginalPrice())  
                .deductionPrice(new BigDecimal("0.00"))  
                .targetCount(groupBuyActivityDiscountVO.getTarget())  
                .startTime(groupBuyActivityDiscountVO.getStartTime())  
                .endTime(groupBuyActivityDiscountVO.getEndTime())  
                .isVisible(false)  
                .isEnable(false).build();  
    }  

    @Override  
    public StrategyHandler<MarketProductEntity, DefaultActivityStrategyFactory.DynamicContext, TrialBalanceEntity> get(MarketProductEntity requestParameter, DefaultActivityStrategyFactory.DynamicContext dynamicContext) {  
        return defaultStrategyHandler;  
    }  
}

业务流转流程梳理

1. 入口调用

  • 业务入口为 IndexGroupBuyMarketService.indexMarketTrial(MarketProductEntity) 方法。
  • 该方法通过 DefaultActivityStrategyFactory.strategyHandler() 获取策略处理器(实际为 RootNode),并调用其 apply 方法。

2. 规则树主流程

  • RootNode 作为规则树的根节点,首先校验入参合法性,然后调用 router 方法。
  • router 方法通过 get 方法获取下一个节点(此处为 SwitchNode),并继续调用其 apply 方法。
  • SwitchNode 作为流程分支节点,直接路由到 MarketNode
  • MarketNode 负责异步加载拼团活动和商品信息(通过多线程分别查询 GroupBuyActivityDiscountVO 和 SkuVO),并将结果写入上下文 DynamicContext
  • 数据加载完成后,MarketNode 继续路由到 EndNode
  • EndNode 作为流程终点节点,从上下文中获取前置查询结果,组装并返回最终的 TrialBalanceEntity 结果。

3. 数据流动说明

  • 入参 MarketProductEntity 贯穿全流程。
  • 异步查询得到的 GroupBuyActivityDiscountVOSkuVO 被写入 DynamicContext,供后续节点使用。
  • 最终返回的 TrialBalanceEntityEndNode 组装,包含商品和活动相关的核心信息。

4. 节点关系图

Text Only
1
2
3
4
5
6
7
8
9
IndexGroupBuyMarketService
RootNode(参数校验)
SwitchNode(流程分支)
MarketNode(异步加载数据)
EndNode(组装返回结果)

规则树设计模式相关面试题

面试题详细解答

1. 规则树设计模式与责任链模式有何异同?各自适用哪些场景?

异同点: - 相同点:两者都属于行为型设计模式,核心思想都是将复杂的业务流程拆分为多个节点,每个节点负责处理自身的逻辑,并决定是否继续传递。 - 不同点: - 责任链模式节点之间是一条线性链路,每个节点只能有一个后继节点,流程固定,扩展性有限。 - 规则树模式则是多分支链路,每个节点可以根据业务动态决定后续节点,支持树状结构,扩展性和灵活性更强。 适用场景: - 责任链适合流程固定、节点顺序明确的场景,如审批流、日志处理等。 - 规则树适合流程复杂、分支多变、需要动态组装的业务场景,如营销活动、风控决策、复杂规则引擎等。

2. 如何通过泛型设计提升规则树模板的通用性?

通过在抽象模板中引入泛型参数(如 T 入参、D 动态上下文、R 返参),可以让规则树模型适配不同的业务类型。这样,调用方只需指定具体的类型即可复用同一套规则树框架,无需重复造轮子,极大提升了模板的通用性和可扩展性。

3. 在规则树模式下,如何实现节点的灵活扩展和动态组装?

  • 每个节点实现 StrategyHandler 或继承抽象节点基类,通过工厂或配置灵活注册。
  • 节点之间通过 get 方法动态决定下一个节点,可以根据业务参数、上下文等灵活路由。
  • 新增节点只需实现对应接口并在工厂或配置中注册,无需修改原有节点代码,符合开闭原则。

4. 规则树节点间如何进行数据传递?请结合 DynamicContext 说明。

  • 通过 DynamicContext 动态上下文对象贯穿整个规则树流程。
  • 每个节点可将自身处理结果、查询到的数据等写入 DynamicContext,后续节点可直接读取使用,实现数据共享与传递。
  • 这样避免了参数过多、方法签名臃肿的问题,也便于扩展和维护。

5. 异步资源加载在规则树中的应用场景及实现方式?

  • 应用场景:如节点需要并发查询多个外部资源(数据库、接口等),且这些资源之间无强依赖关系时,可采用异步加载提升整体性能。
  • 实现方式:在节点的 multiThread 方法中,利用线程池并发执行多个查询任务,结果通过 FutureTask 等方式收集,最后统一写入 DynamicContext

6. 如果某个节点需要根据业务动态决定后续节点,如何实现?

  • 节点实现 get 方法时,根据入参和上下文的实际业务数据,动态选择并返回下一个节点实例。
  • 例如:可根据用户类型、商品类型、活动状态等条件,决定流程分支走向,实现灵活的业务编排。

7. 规则树模式下如何处理异常和兜底逻辑?

  • 每个节点可在自身逻辑中捕获并处理异常,必要时抛出自定义异常由上层统一处理。
  • 抽象基类中可设置 defaultStrategyHandler 兜底处理器,当未匹配到合适节点或发生异常时,自动走兜底逻辑,保证流程健壮性。

8. 请简述策略映射器(StrategyMapper)和策略处理器(StrategyHandler)的职责分工。

  • StrategyMapper 负责根据参数和上下文选择合适的策略节点(即流程路由)。
  • StrategyHandler 负责具体的业务处理逻辑(即节点受理)。
  • 两者解耦,便于扩展和维护。

9. 结合实际业务,如何将规则树模式与工厂模式结合使用?

  • 通过工厂模式统一管理和生产规则树的各个节点实例(如 RootNode、SwitchNode、MarketNode 等)。
  • 工厂负责节点的注册、依赖注入和生命周期管理,业务入口只需通过工厂获取根节点即可。
  • 这样可以实现节点的灵活组装、替换和扩展,提升系统的可维护性。

10. 规则树设计模式在高并发场景下的优势和注意事项有哪些?

优势: - 节点可并发处理无依赖的数据加载任务,充分利用多核资源,提升吞吐量。 - 结构清晰,便于拆分和扩展,适合复杂高并发业务场景。 注意事项: - 异步任务需合理设置超时时间,防止线程池资源耗尽。 - 动态上下文对象需保证线程安全,避免并发读写冲突。 - 节点间数据传递要清晰,避免数据遗漏或重复处理。