策略模式:把算法装进“可插拔”的盒子里
策略模式,说白了就是——定义一系列算法,把它们一个个封装好,然后让它们可以互相替换。你想想看,我们写代码时经常遇到这种情况:同一个功能,有好几种实现方式。比如排序,你可以用冒泡、快排、归并;比如支付,你可以用微信、支付宝、银行卡。如果把这些逻辑全塞在一个大if-else里,那代码迟早变成一锅粥。
我个人习惯,遇到这种“多选一”的场景,第一反应就是策略模式。它不是什么高深的东西,但用好了,代码会变得特别清爽。
为什么需要策略模式?
先看一个反面教材。假设你写了一个订单系统,要根据用户等级计算折扣:
public double calculateDiscount(String level, double amount) {
if ("VIP".equals(level)) {
return amount * 0.8;
} else if ("GOLD".equals(level)) {
return amount * 0.85;
} else if ("SILVER".equals(level)) {
return amount * 0.9;
} else {
return amount * 0.95;
}
}
这段代码有什么问题?
- 每次新增一个等级,都要改这个函数
- 多个地方用到折扣计算时,逻辑会重复
- 测试起来很麻烦,得把所有分支跑一遍
我在项目中遇到过类似的情况。当时是一个促销活动模块,运营同学每周都要调整折扣策略。每次改代码、发版,折腾得够呛。后来我重构成了策略模式,运营直接在后台配置策略类,再也不用等开发排期了。
策略模式的核心结构
策略模式其实就三个角色:
- 策略接口:定义所有算法共有的方法
- 具体策略:实现接口,封装各自的算法
- 上下文:持有策略接口的引用,负责调用
嗯,这里要注意:上下文不负责实现算法,它只负责“用”算法。就像你点外卖,你只关心吃什么(调用策略),不关心厨师怎么做(算法实现)。
核心思想:把算法的定义和使用分离。算法可以独立变化,客户端不用关心具体实现。
用代码说话
还是刚才的折扣计算,我们用策略模式重写一遍。
先定义策略接口:
public interface DiscountStrategy {
double calculate(double amount);
}
然后实现几个具体策略:
public class VIPDiscount implements DiscountStrategy {
@Override
public double calculate(double amount) {
return amount * 0.8;
}
}
public class GoldDiscount implements DiscountStrategy {
@Override
public double calculate(double amount) {
return amount * 0.85;
}
}
public class SilverDiscount implements DiscountStrategy {
@Override
public double calculate(double amount) {
return amount * 0.9;
}
}
最后是上下文:
public class OrderContext {
private DiscountStrategy strategy;
public OrderContext(DiscountStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double getFinalAmount(double amount) {
return strategy.calculate(amount);
}
}
客户端调用:
OrderContext context = new OrderContext(new VIPDiscount());
double finalAmount = context.getFinalAmount(100.0);
// 输出 80.0
// 想换策略?直接换
context.setStrategy(new GoldDiscount());
finalAmount = context.getFinalAmount(100.0);
// 输出 85.0
你看,现在新增一个等级,只需要新增一个策略类,完全不用改现有代码。这就是开闭原则的体现——对扩展开放,对修改关闭。
策略模式的应用场景
我个人总结,以下情况特别适合用策略模式:
| 场景 | 说明 |
|---|---|
| 支付方式 | 微信、支付宝、银行卡、余额……每种支付逻辑不同 |
| 排序算法 | 根据数据量大小选择冒泡、快排、归并 |
| 数据导出 | 导出为Excel、PDF、CSV,格式不同但流程相似 |
| 促销活动 | 满减、打折、赠品、积分……策略经常变化 |
| 用户权限 | 不同角色看到不同菜单、执行不同操作 |
小提示:如果策略只有两三个,而且几乎不会变化,用简单的if-else反而更直观。别为了用模式而用模式。
避坑指南
我曾经在一个项目中踩过坑。当时把策略类全部用静态工厂方法创建,结果策略多了以后,工厂类变得巨大无比,每次新增策略都要改工厂。后来我改用注册表模式,把策略名称和类映射关系放在配置文件里,用反射动态加载。这样新增策略只需要加配置,完全不用改代码。
另外,策略模式有一个潜在问题:客户端必须知道所有策略的区别,才能选择合适的策略。如果策略太多,客户端会变得很重。这时候可以考虑结合简单工厂模式,把策略的选择逻辑封装起来。
注意:策略模式会增加类的数量。如果每个策略只有几行代码,却要新建一个类,那就过度设计了。可以用匿名内部类或Lambda表达式简化。
策略模式的核心逻辑图
下面这张图展示了策略模式的整体结构。你可以看到,上下文只依赖策略接口,具体策略之间互不干扰,可以独立替换。
策略模式的优缺点
任何模式都有两面性,策略模式也不例外。
优点:
- 符合开闭原则,新增策略不用改现有代码
- 避免大量的if-else,代码更清晰
- 策略可以独立测试,提高可测性
- 运行时可以动态切换策略,灵活性高
缺点:
- 类数量增加,每个策略一个类
- 客户端必须了解所有策略的区别
- 策略之间无法共享状态(但可以通过上下文传递)
我的建议:策略模式最适合那些“算法经常变化”或“算法数量较多”的场景。如果算法稳定且数量少,别硬套模式。代码是给人读的,不是给模式看的。
总结
策略模式的核心就一句话:把算法封装成对象,让它们可以互相替换。它让我们的代码从“硬编码”走向“可配置”,从“修改”走向“扩展”。
我记得有一次重构一个支付模块,原来有十几个if-else,每次新增支付方式都要改核心代码。改成策略模式后,新增支付方式只需要写一个新类,然后在配置中心注册一下。运营同学自己就能上线新支付方式,再也不用半夜找我发版了。
嗯,这就是策略模式的价值——让变化变得简单,让代码变得干净。