从IF地狱到策略模式:一个订单折扣场景的重构实战(附完整案例代码)
目录导读
- 为什么你需要策略模式? —— 从一段令人窒息的if-else代码说起
- 策略模式核心解剖 —— 三个角色的职责与协作关系图
- 实战案例:电商订单折扣系统 —— 完整Java代码演示(含UML逻辑)
- 策略模式 vs 工厂模式 vs 状态模式 —— 一张表看懂区别与适用场景
- 高频面试问答 —— 策略模式如何消除if-else?如何与Spring结合?
- 避坑指南 —— 哪些场景千万别用策略模式
为什么你需要策略模式?从一段令人窒息的代码说起
假设你正在开发一个电商订单系统,产品经理提了需求:根据用户等级(普通、VIP、超级VIP)计算订单折扣,第一版代码,你可能会这样写:

public double calculateDiscount(Order order) {
String userLevel = order.getUser().getLevel();
double amount = order.getAmount();
if ("NORMAL".equals(userLevel)) {
return amount * 0.95; // 95折
} else if ("VIP".equals(userLevel)) {
return amount * 0.90; // 9折
} else if ("SUPER_VIP".equals(userLevel)) {
return amount * 0.80; // 8折
} else {
return amount * 1.0; // 无折扣
}
}
刚开始,这段代码简洁明了,但第二周,产品经理说:“新增‘钻石会员’,打75折,并且生日当天额外减50元。”第三周,“运营活动:双十一全场85折,叠加会员折扣。”第四周,“老客户回馈:消费满1000减200,但仅限普通用户。”
你的calculateDiscount方法已经变成上百行的if-else嵌套,每次改动,你都要小心翼翼,因为任何一行都会影响所有用户。这就是典型的“策略爆炸”问题——算法(折扣逻辑)和上下文(订单、用户)高度耦合。
策略模式 正是为了解决这类“多种算法可互换”的场景而生,它定义算法族,分别封装起来,让它们之间可以互相替换,且不影响调用方,简单说:把变化的算法抽出来,变成一个个独立的策略类。
策略模式核心解剖:三个角色与协作关系
策略模式包含三个核心角色:
- 策略接口(Strategy):定义算法的公共接口,例如
DiscountStrategy。 - 具体策略(ConcreteStrategy):实现策略接口的具体算法类,例如
VipDiscountStrategy、SuperVipDiscountStrategy。 - 上下文(Context):持有策略接口的引用,负责维护对具体策略的调用,上下文不直接实现算法,而是将算法委托给策略对象。
协作流程如下:
客户端 → 上下文(Context)→ 策略接口(Strategy)→ 具体策略实现
关键点在于:上下文只依赖策略接口,不依赖任何具体策略,这样,新增算法时,只需新增具体策略类,无需修改上下文和客户端代码——满足开闭原则(OCP)。
实战案例:电商订单折扣系统(完整Java代码)
定义策略接口
public interface DiscountStrategy {
/**
* 计算折扣后的金额
* @param order 订单对象(包含金额、用户信息等)
* @return 实际应付金额
*/
double calculate(Order order);
}
实现具体策略类
// 普通会员:95折
public class NormalDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(Order order) {
return order.getAmount() * 0.95;
}
}
// VIP会员:9折
public class VipDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(Order order) {
return order.getAmount() * 0.90;
}
}
// 超级VIP:8折
public class SuperVipDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(Order order) {
return order.getAmount() * 0.80;
}
}
// 新增:钻石会员,75折(无需修改任何现有代码,只需新增一个类)
public class DiamondDiscountStrategy implements DiscountStrategy {
@Override
public double calculate(Order order) {
return order.getAmount() * 0.75;
}
}
定义上下文(Context)—— 订单服务
public class OrderService {
private final DiscountStrategy discountStrategy;
// 通过构造器注入策略对象
public OrderService(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
// 可选:提供setter方法用于运行时动态切换策略
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double checkout(Order order) {
// 委托给策略对象计算折扣
return discountStrategy.calculate(order);
}
}
客户端使用示例
public class Client {
public static void main(String[] args) {
Order order = new Order();
order.setAmount(1000.0);
order.setUserLevel("VIP");
// 根据用户等级创建对应策略
DiscountStrategy strategy = null;
String level = order.getUserLevel();
if ("NORMAL".equals(level)) {
strategy = new NormalDiscountStrategy();
} else if ("VIP".equals(level)) {
strategy = new VipDiscountStrategy();
} else if ("SUPER_VIP".equals(level)) {
strategy = new SuperVipDiscountStrategy();
}
OrderService service = new OrderService(strategy);
double finalAmount = service.checkout(order);
System.out.println("最终应付金额:" + finalAmount);
}
}
注意:上面的客户端可能仍然有if-else来创建策略对象,这时候可以结合简单工厂或Spring容器来彻底消除客户端分支,在Spring中,你可以将每个策略注册为Bean,然后通过Map<String, DiscountStrategy>自动注入。
Spring中优雅使用策略模式(进阶)
@Service
public class DiscountStrategyFactory {
@Autowired
Map<String, DiscountStrategy> strategyMap; // Spring会注入所有策略Bean,key为Bean名称
public DiscountStrategy getStrategy(String discountType) {
return strategyMap.get(discountType);
}
}
这样,客户端只需要调用factory.getStrategy("vipDiscountStrategy")即可获取对应策略,彻底消除if-else。
策略模式 vs 工厂模式 vs 状态模式
| 模式 | 核心解决 | 关键区别 | 典型应用 |
|---|---|---|---|
| 策略模式 | 算法族的切换 | 上下文与算法解耦,客户端决定策略,算法可互换 | 打折、排序、压缩算法 |
| 工厂模式 | 对象的创建 | 封装创建逻辑,返回实例(可能对应多个策略) | 替代直接new对象 |
| 状态模式 | 对象状态导致的内部行为变化 | 状态切换由对象内部触发,策略切换由外部触发 | 订单状态机(待付款→已付款→已发货) |
关键一句话:策略模式关注“如何做”,工厂模式关注“创建什么”,状态模式关注“何时切换并把行为摊入状态”。
实战建议:当你在写if (type == A) doX; else if (type == B) doY;,并且分支里的逻辑超过3行且每个分支相互独立时,考虑策略模式,如果分支只是简单的对象创建,用工厂模式即可。
高频面试问答
Q1:策略模式一定消除了所有的if-else吗? A:不,策略模式消除了“算法内部”的条件判断,但客户端在“选择用哪个策略”时,可能还需要if-else或switch,完全消除需要结合工厂模式+注册表(如Spring的Map注入)或枚举。
Q2:策略模式与多态有什么区别? A:多态是面向对象的基本特性(方法重写),策略模式是多态的一种典型应用,策略模式强调“将算法封装为对象”,并且这些对象可以独立于客户端变化,你可以在不使用策略模式时就使用多态(如继承重写),但策略模式更强调“组合优于继承”,通过接口+组合实现算法的自由切换。
Q3:策略模式会造成类爆炸吗?如果策略很多怎么办?
A:确实会增加类数量,解决方案包括:使用匿名内部类、Lambda表达式(参考现有策略接口,用DiscountStrategy s = order -> order.getAmount() * 0.95)或者Enum枚举内嵌策略逻辑,Lambda是Java 8中策略模式的极简写法,适合简单算法。
Q4:在Spring中,如何为同一接口的多个实现自动注入策略?
A:通过@Autowired注入Map<String, Interface>,key是Bean的名称(类名首字母小写),value是实例,或者使用List<Interface>,然后你自行选择,推荐前者。
避坑指南:哪些场景千万别用策略模式
- 算法极少且几乎不变:比如只判断一个bool值然后返回固定折扣,不要套模式。
- 策略之间有公共逻辑且状态需要共享:策略模式强调算法独立,如果各策略依赖大量公共状态,不如使用模板方法模式。
- 客户端需要频繁切换策略且每次切换都涉及复杂配置:这时可考虑状态模式或构建者模式。
- 性能极端敏感:策略对象会增加一个间接调用层,通常可以忽略,但如果是在超高频循环中(如每秒百万次),建议谨慎。
策略模式是Java开发中“消除if-else”的最强工具之一,它通过封装算法,让代码更优雅、更符合开闭原则,但记住,它不是万能的,也不应该为了模式而模式,当你的代码中出现频繁的算法分支,并且这些算法未来可能扩展时,策略模式就是你的不二之选,配合Spring的IOC和Lambda,你可以写出高效且易于维护的代码。
核心金句:策略模式不是消灭变化,而是把变化封装起来,让变化不再波及全局。