策略模式:算法族封装与工厂模式结合

策略模式,说白了就是「把算法打包成独立的策略,让它们可以互相替换」。我最早接触这个模式时,觉得它不就是多态吗?后来在项目中踩过坑才明白——它解决的是「行为选择」的问题,而不是「对象创建」的问题。

举个例子。你写了一个电商系统,订单价格计算需要支持多种优惠策略:满减、打折、VIP折扣。如果把这些逻辑全塞进一个if-else里,那代码会变成什么样?嗯,我见过一个项目,光价格计算就写了300行if-else,每次加新活动都要改核心代码,测试回归能跑一整天。

策略模式的核心结构

策略模式有三个角色:

  • 策略接口:定义算法的统一规范
  • 具体策略:实现不同的算法变体
  • 上下文:持有策略引用,负责调用

你看,这其实就是一个「面向接口编程」的典型应用。我习惯把策略接口设计成函数式接口,这样Java 8的Lambda也能直接当策略用。

核心要点:策略模式把「变化的行为」从「不变的上下文」中抽离出来。上下文只负责调用,不关心具体怎么算。

代码示例:价格计算策略

先看一个最基础的实现。假设我们要处理订单的价格计算:

// 策略接口
public interface DiscountStrategy {
    BigDecimal apply(BigDecimal price);
}

// 具体策略:满减
public class FullReductionStrategy implements DiscountStrategy {
    private BigDecimal threshold;
    private BigDecimal reduction;
    
    public FullReductionStrategy(BigDecimal threshold, BigDecimal reduction) {
        this.threshold = threshold;
        this.reduction = reduction;
    }
    
    @Override
    public BigDecimal apply(BigDecimal price) {
        if (price.compareTo(threshold) >= 0) {
            return price.subtract(reduction);
        }
        return price;
    }
}

// 具体策略:打折
public class PercentageStrategy implements DiscountStrategy {
    private double rate; // 0.8 表示八折
    
    public PercentageStrategy(double rate) {
        this.rate = rate;
    }
    
    @Override
    public BigDecimal apply(BigDecimal price) {
        return price.multiply(BigDecimal.valueOf(rate));
    }
}

// 上下文
public class Order {
    private DiscountStrategy strategy;
    
    public void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }
    
    public BigDecimal calculatePrice(BigDecimal originalPrice) {
        return strategy.apply(originalPrice);
    }
}

这样写的好处是什么?你想想看,如果以后要加「满两件打八折」的策略,只需要新增一个类,完全不用改Order类。我在项目中遇到过最头疼的事,就是每次加活动都要改核心业务类,改完还要全量回归。用了策略模式后,新策略单独测试就行。

策略模式与工厂模式结合

上面的代码有个问题——客户端需要自己创建策略对象。比如:

Order order = new Order();
order.setStrategy(new FullReductionStrategy(new BigDecimal("200"), new BigDecimal("30")));
order.calculatePrice(new BigDecimal("250"));

这其实把「策略选择」的责任推给了调用方。如果策略很多,调用方就得写一堆if-else来创建策略。嗯,这不又回到老路上了吗?

我的做法是:用工厂模式来封装策略的创建。这样调用方只需要传一个策略类型,工厂负责返回对应的策略实例。

public class DiscountStrategyFactory {
    private static final Map<String, DiscountStrategy> strategies = new HashMap<>();
    
    static {
        strategies.put("full200_30", new FullReductionStrategy(
            new BigDecimal("200"), new BigDecimal("30")));
        strategies.put("full300_50", new FullReductionStrategy(
            new BigDecimal("300"), new BigDecimal("50")));
        strategies.put("eight_percent", new PercentageStrategy(0.8));
        strategies.put("seven_percent", new PercentageStrategy(0.7));
    }
    
    public static DiscountStrategy getStrategy(String type) {
        DiscountStrategy strategy = strategies.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的策略类型: " + type);
        }
        return strategy;
    }
}

// 客户端使用
Order order = new Order();
order.setStrategy(DiscountStrategyFactory.getStrategy("full200_30"));
BigDecimal finalPrice = order.calculatePrice(new BigDecimal("250"));

你看,这样调用方就完全不需要知道策略的创建细节了。工厂内部可以预置一些常用策略,也可以支持动态注册。我曾经在一个促销系统中,把策略工厂和配置中心结合,运营人员直接在后台配置策略参数,系统自动注册到工厂里。

个人经验:策略工厂里可以放一个默认策略。如果传入的类型找不到,就返回默认策略(比如不打折)。这样系统不会因为配置错误而崩溃。

策略模式的适用场景

不是所有地方都需要策略模式。我总结了几条判断标准:

  • 有多个相似的算法:比如排序算法、支付方式、验证规则
  • 算法需要动态切换:运行时根据条件选择不同算法
  • 算法独立于客户端:算法本身不依赖调用方的状态

举个例子。我做过一个数据导出系统,支持导出成Excel、CSV、PDF。每种格式的导出逻辑完全不同,但对外接口是一样的。这就是策略模式的典型场景。

避坑指南:我曾经在一个项目中,把策略模式用在了「只有一种实现」的地方。结果代码多了一层抽象,反而增加了复杂度。记住,策略模式是为「变化」而生的,如果算法不会变,就别用。

策略模式与状态模式的区别

很多人会把策略模式和状态模式搞混。我简单说一下区别:

维度 策略模式 状态模式
关注点 算法的选择 状态的切换
行为变化 由外部决定 由内部状态决定
策略/状态数量 通常固定 可能动态变化
典型场景 价格计算、排序 订单状态流转、工作流

说白了,策略模式是「我告诉你用什么算法」,状态模式是「我根据当前状态决定用什么行为」。两者虽然结构相似,但意图完全不同。

策略模式在Java标准库中的应用

其实你每天都在用策略模式,只是没意识到。比如:

  • Comparator接口:不同的比较策略决定了排序结果
  • ThreadPoolExecutor的拒绝策略:AbortPolicy、CallerRunsPolicy等
  • LayoutManager:不同的布局策略决定了组件排列方式

我记得有一次排查线上问题,发现某个接口响应特别慢。一看代码,里面用了大量的if-else来判断不同的业务类型。我把它改成了策略模式+工厂,每个策略单独测试,性能问题一下子就定位到了——原来是某个策略里调了一个慢查询。

策略模式的优缺点

任何模式都有两面性。我客观说一下:

优点

  • 算法可以独立变化,符合开闭原则
  • 避免大量的if-else,代码更清晰
  • 方便单元测试,每个策略可以单独测

缺点

  • 策略类会增多,管理成本上升
  • 客户端需要了解策略之间的区别
  • 如果策略很少变化,过度设计反而增加复杂度

我的建议:当你有3个以上的相似算法,并且它们可能会增加或变化时,果断用策略模式。如果只有2个,先考虑用简单的if-else,等第三个出现时再重构。

策略模式的核心逻辑图

下面这张图展示了策略模式的核心流程:上下文持有策略接口,具体策略实现算法,工厂负责创建策略。

策略模式核心结构 客户端 调用 策略工厂 返回策略 策略接口 具体策略A 具体策略B

这张图你看懂了吗?客户端不直接创建策略,而是通过工厂获取。策略接口定义了统一的调用规范,具体策略各自实现不同的算法。这样,客户端和具体策略之间就解耦了。

总结

策略模式的核心思想就一句话:把算法封装成独立的策略,让它们可以互相替换。结合工厂模式后,策略的创建和选择完全由工厂负责,客户端只需要告诉工厂「我要什么策略」就行了。

我在实际项目中,经常把策略模式和模板方法模式一起用。模板方法定义算法的骨架,策略模式提供可替换的步骤。这样既保证了流程的稳定性,又保留了扩展的灵活性。

嗯,最后说一句:设计模式不是银弹。用之前先问问自己——这里真的需要策略模式吗?如果答案是肯定的,那就大胆用。如果只是「为了用模式而用模式」,那还是省省吧。


公众号:蓝海资料掘金营,微信deep3321