策略模式:让算法像乐高积木一样自由拼插
说实话,我最早接触策略模式时,觉得它就是个「花架子」。直到有一次在支付系统里踩了坑,才真正明白它的价值。那是个电商项目,支付方式从微信、支付宝到银联,每种支付的计算逻辑都不一样。一开始我用 if-else 硬写,结果每次新增支付方式都要改核心代码,改到后面我自己都怕了。
嗯,策略模式就是来解决这个问题的。它把「做什么」和「怎么做」拆开,让算法可以独立变化。你想想看,这不就是现实中的「策略」吗?打仗有作战策略,下棋有棋路策略,代码里也一样。
策略模式的核心三要素
说白了,策略模式就三个角色:
- 策略接口(Strategy):定义算法的统一规范。比如「支付」这个动作,不管用哪种方式,最终都是要扣钱。
- 具体策略(ConcreteStrategy):实现策略接口,每个策略封装一种算法。微信支付、支付宝支付各写各的。
- 上下文(Context):持有策略接口的引用,负责调用策略。它不关心具体怎么算,只负责「用」。
我个人的习惯是,先把策略接口定义清楚,再写具体实现。上下文反而最后写,因为它只是个「调度员」。
支付方式选择:最经典的案例
先看一个支付场景。假设我们要做一个支付模块,支持微信、支付宝、银联三种方式。传统写法是这样的:
// 传统 if-else 写法(不推荐)
public class PaymentService {
public void pay(String type, double amount) {
if ("wechat".equals(type)) {
System.out.println("微信支付:" + amount);
} else if ("alipay".equals(type)) {
System.out.println("支付宝支付:" + amount);
} else if ("unionpay".equals(type)) {
System.out.println("银联支付:" + amount);
} else {
throw new IllegalArgumentException("不支持的支付方式");
}
}
}
这种写法有什么问题?我遇到过最头疼的情况:产品经理说「再加个京东支付」。好,我打开 PaymentService,加一个 else if。改完发现微信支付的逻辑也要微调,又得改同一段代码。改来改去,这个类越来越臃肿,测试用例也越来越难写。
用策略模式重构一下:
// 策略接口
public interface PaymentStrategy {
void pay(double amount);
}
// 具体策略:微信支付
public class WechatPay implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("微信支付:" + amount + "元");
// 微信特有的签名、回调逻辑
}
}
// 具体策略:支付宝支付
public class AlipayPay implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("支付宝支付:" + amount + "元");
// 支付宝特有的验签逻辑
}
}
// 上下文
public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePay(double amount) {
strategy.pay(amount);
}
}
// 使用
public class Client {
public static void main(String[] args) {
PaymentContext context = new PaymentContext(new WechatPay());
context.executePay(100.0);
}
}
核心变化:新增支付方式时,只需要新增一个策略类,不需要改任何已有代码。这就是「开闭原则」——对扩展开放,对修改关闭。
排序算法切换:另一个实战场景
我记得有一次做数据分析平台,用户上传的数据量差异很大。小数据用冒泡排序就够了,大数据必须用快速排序。如果写死一种排序算法,要么小数据浪费性能,要么大数据跑不动。
策略模式正好派上用场:
// 策略接口
public interface SortStrategy {
void sort(int[] array);
}
// 具体策略:冒泡排序
public class BubbleSort implements SortStrategy {
@Override
public void sort(int[] array) {
System.out.println("使用冒泡排序");
// 冒泡排序实现...
}
}
// 具体策略:快速排序
public class QuickSort implements SortStrategy {
@Override
public void sort(int[] array) {
System.out.println("使用快速排序");
// 快速排序实现...
}
}
// 上下文
public class SortContext {
private SortStrategy strategy;
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void executeSort(int[] array) {
strategy.sort(array);
}
}
// 运行时动态切换
public class Client {
public static void main(String[] args) {
SortContext context = new SortContext();
int[] data = {5, 3, 8, 1, 9};
if (data.length < 100) {
context.setStrategy(new BubbleSort());
} else {
context.setStrategy(new QuickSort());
}
context.executeSort(data);
}
}
我的经验:策略模式特别适合「算法族」场景。比如压缩算法(zip/gzip/rar)、加密算法(AES/RSA/SM4)、折扣计算(满减/打折/返现)。只要发现代码里有大量 if-else 判断「类型」然后执行不同逻辑,就可以考虑用策略模式。
策略模式的优缺点
| 优点 | 缺点 |
|---|---|
| 符合开闭原则,新增策略不影响现有代码 | 策略类数量会增多,需要管理 |
| 避免多重条件判断,代码更清晰 | 客户端必须了解所有策略的区别 |
| 算法可以自由切换,运行时动态改变 | 策略类之间无法共享数据(除非用上下文传递) |
避坑指南:我曾经在一个项目中过度使用策略模式,把只有两种分支的逻辑也拆成策略。结果策略类膨胀到几十个,维护起来反而更麻烦。记住:策略模式适合「算法族」或「行为族」,如果只有两三种变化,用简单的 if-else 或者枚举反而更合适。
策略模式的结构图
什么时候用策略模式?
我总结了几条判断标准:
- 代码里有大量 if-else 或 switch-case,而且每个分支做的事情完全不同。
- 算法经常变化,或者未来可能增加新的算法。
- 算法之间相互独立,不需要共享状态。
- 客户端需要动态选择算法,比如根据用户配置或数据量大小。
举个例子,我最近做的一个报表系统,导出格式有 PDF、Excel、CSV。每种格式的生成逻辑完全不同,而且客户经常要求新增格式。用策略模式后,每次新增格式只需要写一个策略类,然后注册到工厂里,主流程一行代码都不用改。
一句话总结:策略模式就是把「怎么做」封装起来,让「谁来做」和「做什么」解耦。它不复杂,但用好了能让代码优雅很多。
嗯,策略模式就聊到这里。记住:设计模式不是银弹,但策略模式绝对是解决「算法族」问题的最优解之一。下次遇到类似的场景,不妨试试看。