让复杂判断逻辑“烟消云散”的实战指南
关键词:策略模式、消除if-else、代码重构、设计模式、可扩展性

目录导读
- 困境重现:复杂判断为什么是代码的“坏味道”?
- 策略模式核心思想:从“指挥官”到“调度员”
- 实战拆解:一个支付场景的重构进化史
- 1 原始代码:if-else的“叠罗汉”
- 2 策略模式重构:抽象、实现与上下文
- 3 为什么这样改更好?——三个维度分析
- 策略模式的“黄金使用场景”与反模式
- 问答环节:开发者最常问的6个策略模式问题
- 拓展进阶:策略模式 + 工厂模式 = 优雅双雄
- SEO友好总结:记住这四个字就够了
困境重现:复杂判断为什么是代码的“坏味道”?
你是否有过这样的经历:接手一个老项目,打开一个核心方法,发现里面是一串长达200行的if-else或者switch-case?看起来像这样:
public double calculate(String type, double amount) {
if ("VIP".equals(type)) {
return amount * 0.8;
} else if ("GOLD".equals(type)) {
return amount * 0.9;
} else if ("SILVER".equals(type)) {
return amount * 0.95;
} else if ("NEW_USER".equals(type)) {
return amount * 0.85;
} else {
return amount;
}
}
这种代码在初期看似简单直接,但随着业务迭代,它会变成团队的噩梦:
- 每个新类型都要加一个else if,方法越变越长
- 修改一处逻辑可能影响其他分支(违反开闭原则)
- 单元测试需要覆盖所有分支,维护成本指数级上升
- 代码可读性极差,新人接手需要从头到尾读完所有条件
核心痛点:我们把“业务类型”和“应用逻辑”强行耦合在一起,导致一旦业务变化,就必须修改已有代码。
策略模式核心思想:从“指挥官”到“调度员”
策略模式(Strategy Pattern)的定义非常简洁:定义一组算法,将每个算法封装起来,并使它们可以互相替换。
通俗理解:我们不再让一个“指挥官”(大方法)亲自处理所有类型的逻辑,而是创建一个“调度员”(上下文类),根据传入的类型,自动选择对应的“士兵”(策略类)去执行任务。
三个关键角色:
- 策略接口(Strategy):定义所有策略必须遵守的“行动契约”
- 具体策略(ConcreteStrategy):实现接口的具体逻辑
- 上下文(Context):持有策略引用,负责调用策略执行
实战拆解:一个支付场景的重构进化史
1 原始代码:if-else的“叠罗汉”
假设我们要实现一个支付系统,支持支付宝、微信、银联三种支付方式:
public class PaymentService {
public void pay(String channel, double amount) {
if ("alipay".equals(channel)) {
System.out.println("调用支付宝API,金额:" + amount);
// 50行具体逻辑
} else if ("wechat".equals(channel)) {
System.out.println("调用微信支付API,金额:" + amount);
// 50行具体逻辑
} else if ("unionpay".equals(channel)) {
System.out.println("调用银联API,金额:" + amount);
// 50行具体逻辑
} else {
throw new RuntimeException("不支持的支付方式");
}
}
}
这种代码:
- 每增加一种支付方式,都要修改
pay方法 - 不同支付方式的逻辑无法复用
- 单元测试需要模拟三种支付API,测试复杂
2 策略模式重构:抽象、实现与上下文
第一步:定义策略接口
public interface PaymentStrategy {
void pay(double amount);
}
第二步:实现具体策略
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("调用支付宝API,金额:" + amount);
// 支付宝特有逻辑
}
}
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("调用微信支付API,金额:" + amount);
// 微信特有逻辑
}
}
public class UnionPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("调用银联API,金额:" + 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) {
// 原来的if-else完全消失了!
String channel = "alipay";
PaymentStrategy strategy = PaymentStrategyFactory.getStrategy(channel);
PaymentContext context = new PaymentContext(strategy);
context.executePay(100.0);
}
}
3 为什么这样改更好?——三个维度分析
| 维度 | 重构前(if-else) | 重构后(策略模式) |
|---|---|---|
| 开闭原则 | 新增支付方式必须修改pay方法 |
新增类实现接口即可,无需修改已有代码 |
| 可测试性 | 需要模拟整个pay方法 |
每个策略可单独测试,mock简单 |
| 可读性 | 200行的大方法难以理解 | 每个策略类只关注自己的逻辑 |
关键变化:业务类型(channel字符串)与业务逻辑(支付步骤)被解耦,新增支付方式时,只需新建一个策略类,并在工厂中注册即可。
策略模式的“黄金使用场景”与反模式
✅ 适合使用策略模式的场景:
- 一个系统中存在大量相互替代的算法或行为
- 算法之间的区别仅在于内部实现,但对外接口一致
- 需要避免使用大量条件分支(if-else / switch-case)
- 算法需要动态切换(根据用户等级选择不同折扣)
❌ 不适合使用策略模式的场景:
- 算法数量极少(比如就2个),且永远不会增加
- 算法逻辑极其简单,使用策略模式反而增加代码复杂度
- 算法之间有大量共享状态(此时考虑模板方法模式更合适)
核心判断标准:如果你不确定未来会不会增加新的分支,那就用策略模式,未来增加分支的概率越高,策略模式的回报越大。
问答环节:开发者最常问的6个策略模式问题
Q1:策略模式会创建大量类,会不会导致类爆炸?
A:这是一个合理的担忧,但实践中,每个策略类的代码通常很少(几十行),类数量的增加是可控的,而且可以通过策略模式 + 枚举或策略模式 + 注册表来减少类数量,如果你有100种折扣策略,但其中90%的逻辑是相同的,可以用参数化策略:
public class DiscountStrategy implements Strategy {
private double discountRate;
public DiscountStrategy(double rate) { this.discountRate = rate; }
// 只需一个策略类,但通过构造参数区分
}
Q2:客户端怎么知道该用哪个策略?是不是还得写if-else?
A:策略的获取逻辑确实需要一个地方来做选择,但这个选择通常被封装进工厂模式或注册表,客户端只需要传入一个类型标识(如枚举或字符串),工厂帮你找到正确的策略,这样,选择逻辑被集中在工厂中,主业务逻辑不受影响。
Q3:策略模式能不能消除所有if-else?
A:不能,策略模式消除的是业务逻辑分支,但路由逻辑(选择哪个策略)仍然需要条件判断,不过路由逻辑通常很薄(一个map的get操作),维护成本远低于业务分支,目标不是消除所有判断,而是把判断从业务逻辑中隔离出去。
Q4:策略模式比if-else性能差吗?
A:通常不会,策略模式只是多了一次接口调用和一次map查询(O(1)),相对于数据库查询或网络IO,这些开销可以忽略不计,如果你有性能洁癖,可以用枚举+函数式接口来替代继承。
Q5:在JavaScript中如何实现策略模式?
A:JavaScript可以利用函数是一等公民的特性,用更简洁的方式实现:
const strategies = {
'alipay': (amount) => console.log(`支付宝支付${amount}`),
'wechat': (amount) => console.log(`微信支付${amount}`),
};
function pay(channel, amount) {
const strategy = strategies[channel];
if (!strategy) throw new Error('不支持的支付方式');
strategy(amount);
}
Q6:策略模式与工厂模式的区别是什么?
A:这是初学者最易混淆的两个模式,简单说:
- 工厂模式:负责创建对象(生产策略)
- 策略模式:负责执行算法(使用策略)
通常两者结合使用:工厂根据输入创建策略对象,策略对象执行具体逻辑。
拓展进阶:策略模式 + 工厂模式 = 优雅双雄
上面的支付示例中,我们提到了PaymentStrategyFactory,这其实就是工厂模式与策略模式的经典组合,它的实现可以是:
public class PaymentStrategyFactory {
private static final Map<String, PaymentStrategy> strategies = new HashMap<>();
static {
strategies.put("alipay", new AlipayStrategy());
strategies.put("wechat", new WechatPayStrategy());
strategies.put("unionpay", new UnionPayStrategy());
}
public static PaymentStrategy getStrategy(String channel) {
PaymentStrategy strategy = strategies.get(channel);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式: " + channel);
}
return strategy;
}
}
效果:客户端调用变成一行代码:
paymentService.executePay(PaymentStrategyFactory.getStrategy(channel), amount);
这种组合的好处:
- 策略的选择逻辑完全从业务代码中剥离
- 新增支付方式:只需在工厂的
put中添加一行,在策略目录下新增一个类 - 可以实现策略的懒加载或缓存,提升性能
SEO友好总结:记住这四个字就够了
如果你只从这篇文章带走一句话,那就是:“把变的部分封装起来,把不变的稳定下来”。
策略模式的本质是为“变化点”建立抽象层,让新业务以“插件”的形式接入系统,而不用动已有逻辑,这种思想不仅适用于支付、折扣、排序等经典场景,也适用于消息推送、数据导出、文件解析、校验规则等几乎所有存在分支判断的场景。
行动清单:
- 下次遇到超过3个if-else,思考是否可以提取成策略
- 先定义策略接口,再实现具体策略,最后用工厂组装
- 单元测试时,每个策略独立测试,覆盖率轻松达到100%
推荐阅读:
- 想深入了解设计模式,可以搜索“GoF 23种设计模式 精讲”
- 想学实战技巧,可以看“重构:改善既有代码的设计”中关于条件分支的部分
*本文由编程架构师团队原创,遵循知识共享-署名-非商业性使用-相同方式共享协议,转载请注明出处:码农架构宝典(请勿添加实际域名,此处为示例)