策略模式:算法族封装与工厂模式结合
策略模式,说白了就是「把算法打包成独立的策略,让它们可以互相替换」。我最早接触这个模式时,觉得它不就是多态吗?后来在项目中踩过坑才明白——它解决的是「行为选择」的问题,而不是「对象创建」的问题。
举个例子。你写了一个电商系统,订单价格计算需要支持多种优惠策略:满减、打折、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,等第三个出现时再重构。
策略模式的核心逻辑图
下面这张图展示了策略模式的核心流程:上下文持有策略接口,具体策略实现算法,工厂负责创建策略。
这张图你看懂了吗?客户端不直接创建策略,而是通过工厂获取。策略接口定义了统一的调用规范,具体策略各自实现不同的算法。这样,客户端和具体策略之间就解耦了。
总结
策略模式的核心思想就一句话:把算法封装成独立的策略,让它们可以互相替换。结合工厂模式后,策略的创建和选择完全由工厂负责,客户端只需要告诉工厂「我要什么策略」就行了。
我在实际项目中,经常把策略模式和模板方法模式一起用。模板方法定义算法的骨架,策略模式提供可替换的步骤。这样既保证了流程的稳定性,又保留了扩展的灵活性。
嗯,最后说一句:设计模式不是银弹。用之前先问问自己——这里真的需要策略模式吗?如果答案是肯定的,那就大胆用。如果只是「为了用模式而用模式」,那还是省省吧。
公众号:蓝海资料掘金营,微信deep3321