小明学架构师的第七课:策略模式:告别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. **本质 **:客户端主动选择算法,运行时可切换

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

接下来,小明还会遇到:

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

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

相关推荐
肖爱Kun17 小时前
C++设计策略模式
开发语言·c++·策略模式
阿宇的技术日志20 小时前
阿里 AI 云诊断系统 Loop Engineering 实战:实现从日志扫描到预发部署的全自主闭环
人工智能·系统架构·loopengineering
圣殿骑士-Khtangc2 天前
传统单体架构改造方案实战:从单体地狱到模块化架构的渐进式演进
系统架构
折哥的程序人生 · 物流技术专研2 天前
Java 23 种设计模式:从踩坑到精通 | 番外:策略 vs 模板方法 —— 组合与继承的终极对决
java·策略模式·java面试·comparator·模版方法模式·java设计模式·从踩坑到精通
折哥的程序人生 · 物流技术专研2 天前
Java 23 种设计模式:从踩坑到精通 | 番外:状态 vs 策略 —— if-else 消除的两条路,你走对了吗?
java·spring·状态模式·策略模式·java面试·java设计模式·从踩坑到精通
消失的旧时光-19433 天前
第七篇:RTK + 地图 + 机器人:完整定位系统架构解析
android·系统架构·机器人·imu·rtk
一几文3 天前
2026年5月软考高级系统架构设计师考试真题回顾解析-综合知识1
架构·系统架构·软考高级·软考·it证书
zhaoshuzhaoshu3 天前
Zephyr 实时操作系统详细介绍
物联网·系统架构
小帽子_1233 天前
EMS 完整系统架构分层详解:感知层、通信层、控制层、应用层
系统架构