行为型模式的开篇之作。
如果要用一句话概括策略模式:
把"怎么做"的选择,从if-else里解放出来。
一个接口,多个实现,运行时随意切换------这就是策略。
一、一句话理解策略
| 模式 | 一句话 | 生活类比 |
|---|---|---|
| 策略 Strategy | 定义一组算法,封装每个算法,让它们可以互相替换 | 导航App:最短路线/避开高速/少收费------目标一样,策略不同 |
💡 策略模式的核心思想:
把一个业务目标拆成多种实现方式,每种方式封装成一个独立的"策略对象"。
上下文(Context)持有策略接口,运行时根据需要切换具体策略。
三个角色:
- Strategy 接口:定义所有策略的统一方法
- ConcreteStrategy 具体策略:每种算法一个类
- Context 上下文:持有策略引用,委托执行
一句话:算法变了,我只换策略对象,不动主流程。
二、小明的if-else地狱
2.1 产品经理的一句话
📖 故事
九月初,老板开完会,拍着小明的肩膀说:
"双十一搞个大的,优惠玩法要丰富!"
产品经理第二天甩来一份需求文档:
① 满200减30
② 全场8折
③ 秒杀价(固定价格)
④ 新人首单5折
⑤ VIP会员9折
⑥ 拼团立减15
⑦ 积分抵扣(100积分=1元)
小明看着这份清单,心想:"不就是7个if-else嘛,半天搞定。"
2.2 代码写成了一堵墙
两天后,代码写完了。
public double calculateDiscount(Order order) {
String type = order.getPromoType();
double price = order.getOriginalPrice();
if ("FULL_REDUCTION".equals(type)) {
// 满200减30
if (price >= 200) {
return price - 30;
}
return price;
} else if ("PERCENT_OFF".equals(type)) {
// 全场8折
return price * 0.8;
} else if ("FLASH_SALE".equals(type)) {
// 秒杀价
return order.getFlashSalePrice();
} else if ("NEW_USER".equals(type)) {
// 新人首单5折
if (userService.isNewUser(order.getUserId())) {
return price * 0.5;
}
return price;
} else if ("VIP".equals(type)) {
// VIP会员9折
if (userService.isVip(order.getUserId())) {
return price * 0.9;
}
return price;
} else if ("GROUP_BUY".equals(type)) {
// 拼团立减15
if (groupBuyService.isInGroup(order.getGroupId())) {
return price - 15;
}
return price;
} else if ("POINTS".equals(type)) {
// 积分抵扣
int points = userService.getPoints(order.getUserId());
double deduction = points / 100.0;
return Math.max(price - deduction, 0);
} else {
return price;
}
}
方法体 80行 。加上各种辅助判断逻辑,整个类 超过500行。
💥 问题来了:
① 测试同事崩溃:每加一种策略,所有分支都要重新回归测试
② 产品经理又来了:"再加一个直播专享价,再加一个品类专属折扣..."
③ 运营说:"满减规则要改成阶梯式的,满100减10,满200减30,满500减80"
④ 小明每次改代码,都怕不小心改坏了别的分支
这就是典型的 违反开闭原则 :每次新增策略,都要修改这个核心方法。改得越多,Bug越多。
三、策略模式重构
3.1 第一步:抽出接口
小明想清楚了:每种优惠算法,本质上都是"输入原价,输出折后价"。
// 策略接口:统一的方法签名
public interface PromotionStrategy {
/**
* 计算优惠后价格
* @param order 订单信息
* @return 优惠后价格
*/
double calculate(Order order);
/**
* 策略类型标识(用于工厂匹配)
*/
String getType();
}
3.2 第二步:每个策略一个类
// 策略1:满减
public class FullReductionStrategy implements PromotionStrategy {
@Override
public double calculate(Order order) {
double price = order.getOriginalPrice();
if (price >= 200) {
return price - 30;
}
return price;
}
@Override
public String getType() {
return "FULL_REDUCTION";
}
}
// 策略2:折扣
public class PercentOffStrategy implements PromotionStrategy {
@Override
public double calculate(Order order) {
return order.getOriginalPrice() * 0.8;
}
@Override
public String getType() {
return "PERCENT_OFF";
}
}
// 策略3:秒杀
public class FlashSaleStrategy implements PromotionStrategy {
@Override
public double calculate(Order order) {
return order.getFlashSalePrice();
}
@Override
public String getType() {
return "FLASH_SALE";
}
}
// 策略4:新人首单
public class NewUserStrategy implements PromotionStrategy {
@Autowired
private UserService userService;
@Override
public double calculate(Order order) {
if (userService.isNewUser(order.getUserId())) {
return order.getOriginalPrice() * 0.5;
}
return order.getOriginalPrice();
}
@Override
public String getType() {
return "NEW_USER";
}
}
// ... 其他策略类类似,每个类只做一件事
✅ 关键变化:
每个策略类只负责一种优惠算法。
改满减规则?只改 FullReductionStrategy 一个类,其他纹丝不动。
新人首单逻辑有Bug?只影响 NewUserStrategy,不会波及VIP和拼团。
3.3 第三步:用Map注册表消灭if-else
策略类写好了,但选择逻辑怎么办?还是得 if-else 判断 type 然后 new 对应的策略?
不。用Map注册表。
@Component
public class PromotionContext {
// 策略注册表:type → 策略实例
private final Map<String, PromotionStrategy> strategyMap
= new HashMap<>();
// Spring 自动注入所有 PromotionStrategy 实现类
@Autowired
public PromotionContext(List<PromotionStrategy> strategies) {
for (PromotionStrategy strategy : strategies) {
strategyMap.put(strategy.getType(), strategy);
}
}
/**
* 执行优惠计算
* 一行代码搞定,没有 if-else
*/
public double applyPromotion(Order order) {
PromotionStrategy strategy = strategyMap
.get(order.getPromoType());
if (strategy == null) {
log.warn("未知优惠类型: {}", order.getPromoType());
return order.getOriginalPrice(); // 默认原价
}
return strategy.calculate(order);
}
}
🔥 这段代码的精髓:
① Spring 启动时,自动扫描所有 PromotionStrategy 的实现类,注入到 List 里
② 构造函数里遍历 List,以 getType() 为 key 注册到 Map
③ 调用时,一行 get(type) 就拿到对应策略,** 零 if-else**
3.4 效果:加新策略只要5分钟
📖 故事
十月中旬,产品经理又来找小明:
"双十一加了直播渠道,要支持'直播专享价'。"
以前:改500行的核心方法,加一个 else if 分支,然后全量回归测试。
现在:
**第一步 **,新建一个类:
public class LiveStreamStrategy implements PromotionStrategy {
@Override
public double calculate(Order order) {
// 直播专享价:原价减20,最低1元
double price = order.getOriginalPrice();
return Math.max(price - 20, 1.0);
}
@Override
public String getType() {
return "LIVE_STREAM";
}
}
**第二步 **,加个 @Component 注解。
完事了。
Spring 启动时自动扫描、自动注册。PromotionContext 里的 strategyMap 自动多了一条 "LIVE_STREAM" 的记录。
小明泡了杯茶,对产品经理说:"上了。"
产品经理:"???不需要改别的代码?"
小明:"不需要。"
四、对比一下
| 维度 | if-else 版本 | 策略模式版本 |
|---|---|---|
| 新增策略 | 修改核心方法,加 else if | 新建一个类,加 @Component |
| 修改策略 | 在500行代码里定位,容易改错 | 改一个类,影响范围明确 |
| 单元测试 | 要覆盖所有分支 | 每个策略类独立测试 |
| 代码行数 | 1个方法80行,整个类500+行 | 每个策略类20-30行 |
| 开闭原则 | ❌ 违反(修改已有代码) | ✅ 符合(扩展新类,不改旧代码) |
| 运行时切换 | 需要重新调方法 | 换策略对象就行 |
五、策略模式的代价
策略模式不是银弹。它也有代价:
5.1 类数量膨胀
7种优惠 = 7个策略类。如果优惠类型有20种,就是20个类。
但换个角度想:** 20个小类 vs 1个2000行的大类,你选哪个?**
5.2 客户端必须知道所有策略
调用方需要知道有哪些策略可选。但这个问题已经被 Map 注册表解决了------前端传 type,后端自动匹配。
5.3 策略之间不能共享逻辑
如果多个策略有相同的判断逻辑(比如都要检查用户身份),可以抽一个抽象基类:
// 抽象策略基类:共享通用逻辑
public abstract class AbstractPromotionStrategy
implements PromotionStrategy {
@Autowired
protected UserService userService;
// 通用方法:检查是否可用
protected boolean isAvailable(Order order) {
return order != null
&& order.getOriginalPrice() > 0;
}
}
// 具体策略继承抽象基类
public class VipStrategy extends AbstractPromotionStrategy {
@Override
public double calculate(Order order) {
if (!isAvailable(order)) return 0;
if (!userService.isVip(order.getUserId())) {
return order.getOriginalPrice();
}
return order.getOriginalPrice() * 0.9;
}
@Override
public String getType() {
return "VIP";
}
}
💡 策略 + 模板方法 = 组合拳
抽象基类提供通用骨架(模板方法),子类覆写差异部分。
这个组合在框架设计中非常常见。后面讲模板方法模式时会展开说。
六、Java 8 函数式简化
如果策略只有一个方法(绝大多数情况),可以用 Lambda 表达式直接简化:
// 不需要定义接口和类,直接用函数
public class PromotionEngine {
// 策略注册表:type → 计算函数
private static final Map<String,
Function<Order, Double>> STRATEGIES
= new HashMap<>();
static {
// 满减:满200减30
STRATEGIES.put("FULL_REDUCTION",
order -> order.getOriginalPrice() >= 200
? order.getOriginalPrice() - 30
: order.getOriginalPrice());
// 8折
STRATEGIES.put("PERCENT_OFF",
order -> order.getOriginalPrice() * 0.8);
// 秒杀
STRATEGIES.put("FLASH_SALE",
order -> order.getFlashSalePrice());
}
public static double apply(String type, Order order) {
Function<Order, Double> strategy =
STRATEGIES.getOrDefault(type,
o -> o.getOriginalPrice()); // 默认原价
return strategy.apply(order);
}
}
✅ 函数式策略的优势:
- 不用写接口、不用写类,一个 Lambda 搞定
- 代码量减少80%
- 适合简单策略(无状态、无外部依赖)
⚠️ 但也有局限:
- 策略逻辑复杂时(超过10行),Lambda 可读性下降
- 需要注入 Service 时(如查用户信息),Lambda 不方便
- 不方便做单元测试
建议: 简单策略用 Lambda,复杂策略用接口+类。
七、策略模式在 JDK 中的应用
你以为策略模式很"高大上"?其实你天天在用:
7.1 Arrays.sort() 的 Comparator
String[] names = {"Charlie", "Alice", "Bob"};
// 策略1:按字母排序
Arrays.sort(names, Comparator.naturalOrder());
// 策略2:按字母倒序
Arrays.sort(names, Comparator.reverseOrder());
// 策略3:按长度排序
Arrays.sort(names, Comparator.comparingInt(String::length));
sort() 方法不关心你怎么排,它只调 Comparator.compare()。
排序算法是固定的,比较策略是你传的。 这就是策略模式。
7.2 ThreadFactory
// 线程池用 ThreadFactory 策略来创建线程
ExecutorService pool = Executors.newFixedThreadPool(
10,
r -> {
Thread t = new Thread(r);
t.setName("my-pool-" + t.getId());
t.setDaemon(true); // 守护线程
return t;
}
);
线程池不关心线程怎么创建,你传什么策略它就怎么创建。
八、软考高频考点
| 考点 | 内容 |
|---|---|
| 模式分类 | 行为型模式 |
| 核心意图 | 定义一系列算法,封装每个算法,使它们可以互相替换 |
| 三个角色 | Strategy(策略接口)、ConcreteStrategy(具体策略)、Context(上下文) |
| 核心原则 | 开闭原则(对扩展开放,对修改关闭) |
| 常见搭配 | 策略 + 工厂(Map注册表)、策略 + 模板方法(抽象基类) |
| 优点 | ① 避免多重条件语句 ② 符合开闭原则 ③ 算法可独立测试和复用 |
| 缺点 | ① 策略类数量增多 ② 客户端需了解所有策略 ③ 仅适用于算法变体场景 |
| 经典案例 | 排序的Comparator、支付方式选择、优惠策略引擎 |
| 易混淆 | 策略 vs 状态:策略是"主动选择",状态是"自动切换" |
🎯 软考高频真题:
"某电商系统支持多种促销策略,要求新增促销方式时不修改已有代码。应采用()设计模式。"
→ 答案:** 策略模式**
"策略模式的主要优点是()"
→ 答案:** 符合开闭原则,可以在不修改原有代码的情况下增加新算法**
九、策略 vs 状态:别搞混了
策略模式和状态模式长得很像(都是接口+多个实现),但本质完全不同:
| 维度 | 策略模式 | 状态模式(下篇预告) |
|---|---|---|
| 谁选择 | 客户端主动选择 | 对象内部自动切换 |
| 是否感知 | 客户端知道用的哪个策略 | 客户端不关心当前状态 |
| 各策略是否独立 | 完全独立,互不知道 | 状态之间知道彼此,负责切换 |
| 典型场景 | 支付方式、优惠算法 | 订单状态机、审批流程 |
💡 一句话区分:
**策略模式 **:你去餐厅,主动选择 吃川菜还是粤菜。
**状态模式 **:订单从"待支付"到"已发货",自动流转 ,你不用管。
十、总结
策略模式是行为型模式的第一块基石,也是最实用的模式之一。
📋 策略模式核心要点:
- **问题 **:多种算法用 if-else 堆在一起,改一个影响全部
- **方案 **:每种算法封装成独立的策略类,通过接口统一调用
- **进阶 **:策略 + 工厂(Map 注册表),消灭选择逻辑
- **简化 **:Java 8 Lambda 适合简单策略
- **搭配 **:抽象基类 + 模板方法,共享通用逻辑
- **本质 **:客户端主动选择算法,运行时可切换
行为型模式的旅程,从策略开始。
接下来,小明还会遇到:
- 订单状态机(状态模式)
- 事件广播系统(观察者模式)
- 审批流程引擎(模板方法 + 责任链)
- ......
每一个,都是他从小白进阶到架构师的必经之路。