← 返回题目列表

策略模式如何处理多个策略组合执行?

高频 中等 第 7 / 26 题 更新于 2026/08/02
策略模式组合策略策略链工程实践

简化版

如果业务不是“从多个策略中选一个”,而是“多个策略按规则一起执行”,就要使用组合策略。组合策略可以把多个子策略封装成一个整体,对外仍然暴露同一个策略接口;内部负责排序、过滤、短路、结果合并和异常处理。优惠叠加、价格计算、风控评分、推荐排序都常见这种设计。

详细版

策略模式的基本形态是选择一个算法。但真实业务里经常需要多个策略协作:

  • 多个优惠同时计算,再选择最优或叠加;
  • 多个风控模型打分,合并成一个风险等级;
  • 多个推荐召回策略返回候选集,再合并去重;
  • 多个运费规则依次修正价格。

这时可以设计组合策略:

class CompositeDiscountStrategy implements DiscountStrategy {
    private final List<DiscountStrategy> delegates;

    public BigDecimal discount(OrderContext context) {
        BigDecimal price = context.originalPrice();
        for (DiscountStrategy strategy : delegates) {
            if (strategy.supports(context)) {
                price = strategy.discount(context.withPrice(price));
            }
        }
        return price;
    }
}

组合策略的重点不是把多个类放进 List,而是明确“顺序、是否短路、结果怎么合并、失败怎么办”。如果这些规则不清楚,组合策略会变成隐藏的业务黑箱。

记忆钩子:普通策略是“选一个”,组合策略是“把多个策略包装成一个策略”。

完整版教学

面试提示:组合策略题要强调“组合规则本身也需要建模”,不要把多个策略随便循环调用就当成设计完成。

一、为什么单策略选择不够用

以优惠计算为例,业务可能要求:

原价 1000
会员折扣 9 折
平台券 -50
满减 -100
运费券 -20
最终价 = 1000 * 0.9 - 50 - 100 - 20 = 730

如果仍然只选一个策略,就无法表达这种叠加规则。策略模式本身不限制只能选一个,关键看你如何组织策略。

二、组合策略仍然实现同一个接口

组合策略可以对外保持统一接口:

interface PriceStrategy {
    Money calculate(PriceContext context);
}

class CompositePriceStrategy implements PriceStrategy {
    private final List<PriceStrategy> strategies;

    public Money calculate(PriceContext context) {
        Money current = context.basePrice();
        for (PriceStrategy strategy : strategies) {
            current = strategy.calculate(context.withCurrentPrice(current));
        }
        return current;
    }
}

调用方看到的还是一个 PriceStrategy,不会感知里面有 3 个、5 个还是 10 个子策略。

三、组合执行必须定义顺序

顺序不同,结果可能不同:

方案 A: 1000 * 0.9 - 100 = 800
方案 B: (1000 - 100) * 0.9 = 810

所以组合策略里要显式排序:

this.strategies = strategies.stream()
    .sorted(Comparator.comparingInt(PriceStrategy::order))
    .toList();

并且要把顺序写进规则文档或配置。例如“先商品级优惠,再订单级优惠,最后支付优惠”。

四、组合策略有多种结果合并方式

组合策略不一定都是串行修改同一个结果,还可能是:

合并方式含义例子
sequential前一个结果传给后一个价格逐步修正
max/min多个结果取最优多张券选优惠最大
merge多个集合合并去重推荐召回
vote多个策略投票风控是否通过
score多个分数加权风险评分

面试中讲清“结果合并模型”,比只说“用 List 循环”更有工程味。

五、组合策略和责任链的区别

组合策略和责任链都可能按顺序执行,但关注点不同:

对比项组合策略责任链
核心意图多个算法合成一个结果请求沿链传递处理
是否都执行通常多个参与可短路,也可继续
对外身份仍然是一个策略是一条处理链
常见例子优惠叠加、评分合成过滤器、审批、拦截

如果你关注“多个算法怎么合并”,偏组合策略;如果你关注“请求谁处理、是否继续传递”,偏责任链。

六、异常处理不能含糊

组合策略里一个子策略失败,不能默认跳过。需要按策略类型定义:

try {
    current = strategy.calculate(context.withCurrentPrice(current));
} catch (Exception ex) {
    if (strategy.required()) {
        throw ex;
    }
    log.warn("optional strategy failed: {}", strategy.name(), ex);
}

例如支付优惠失败可以跳过,但库存锁定失败不能跳过。组合策略里最好给每个子策略标记 requiredordernametimeout

七、组合策略要防止重复执行

如果同一个策略被注册两次,可能造成金额重复扣减:

strategies:
  coupon
  vip
  coupon   <- 重复,可能多扣一次

启动时可以检测:

Set<String> names = new HashSet<>();
for (PriceStrategy strategy : strategies) {
    if (!names.add(strategy.name())) {
        throw new IllegalStateException("duplicate strategy: " + strategy.name());
    }
}

这类校验对金额、积分、库存等场景尤其重要。

八、常见误区与追问

  • 误区:策略模式只能每次选一个策略。 策略可以被组合,对外仍然表现为一个统一策略接口。
  • 误区:组合策略就是简单 for 循环。 真正难点是顺序、短路、合并、异常和幂等。
  • 误区:策略顺序无所谓。 价格、风控、路由等场景顺序会直接影响结果。
  • 误区:子策略失败就跳过。 必选策略失败应中断,可选策略失败才考虑降级。
  • 追问:组合策略和责任链怎么区分? 组合策略重在多个算法合成结果,责任链重在请求传递和处理权。
  • 追问:如何测试组合策略? 测顺序、重复注册、部分失败、空策略列表、边界金额和结果合并。
  • 追问:组合策略会不会过度设计? 策略少且规则稳定时没必要;规则频繁变化、结果合并复杂时才值得。

九、加强记忆

  1. 普通策略是选一个,组合策略是多个策略合成一个结果。
  2. 组合策略本身也实现策略接口。
  3. 组合执行必须定义顺序和合并方式。
  4. 子策略失败要区分必选和可选。
  5. 金额类策略要防重复执行和重复注册。
  6. 与责任链的区别在于重点是“结果合成”还是“请求传递”。