小明学架构师的第七课:策略模式:告别if-else的选择困难症

📅 2026/7/16 1:45:33 👁️ 阅读次数 📝 编程学习
小明学架构师的第七课:策略模式:告别if-else的选择困难症
行为型模式的开篇之作。 如果要用一句话概括策略模式:把"怎么做"的选择,从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 状态:别搞混了

策略模式和状态模式长得很像(都是接口+多个实现),但本质完全不同:

维度策略模式状态模式(下篇预告)
谁选择客户端主动选择对象内部自动切换
是否感知客户端知道用的哪个策略客户端不关心当前状态
各策略是否独立完全独立,互不知道状态之间知道彼此,负责切换
典型场景支付方式、优惠算法订单状态机、审批流程

💡一句话区分:

**策略模式 **:你去餐厅,主动选择吃川菜还是粤菜。 **状态模式 **:订单从"待支付"到"已发货",自动流转,你不用管。

十、总结

策略模式是行为型模式的第一块基石,也是最实用的模式之一。

📋策略模式核心要点:

  1. **问题 **:多种算法用 if-else 堆在一起,改一个影响全部
  2. **方案 **:每种算法封装成独立的策略类,通过接口统一调用
  3. **进阶 **:策略 + 工厂(Map 注册表),消灭选择逻辑
  4. **简化 **:Java 8 Lambda 适合简单策略
  5. **搭配 **:抽象基类 + 模板方法,共享通用逻辑
  6. **本质 **:客户端主动选择算法,运行时可切换

行为型模式的旅程,从策略开始。

接下来,小明还会遇到:

  • 订单状态机(状态模式)
  • 事件广播系统(观察者模式)
  • 审批流程引擎(模板方法 + 责任链)
  • ……

每一个,都是他从小白进阶到架构师的必经之路。