Java 23 种设计模式:从踩坑到精通 | 策略模式 ------ 算法族的封装与切换,告别 if-else
摘要 :当同一个操作有多种算法实现,且需要根据场景灵活切换时,
if-else或switch会将所有算法混杂在一起,导致逻辑臃肿、扩展困难。策略模式将每个算法封装为独立的策略类,使它们可以相互替换,且算法的变化不影响使用算法的客户端。本文从电商促销策略的场景出发,完整讲解策略模式的原理、UML、代码实现、与状态模式的区别,并结合电子面单多平台架构实战、JDK 排序比较器、Spring 资源加载等应用,帮你掌握"算法对象化"的设计精髓。
🗺️ 本文阅读地图(3 分钟速览)
- 为什么一坨
if-else促销代码一定会炸?- 策略模式三大角色拆解
- 手写促销策略引擎(满减、打折、立减)
- Lambda 简化策略模式
- 实战案例:电子面单多平台架构中的三层策略应用
- JDK
Comparator/ SpringResourceLoader如何体现- 面试必问:策略 vs 状态,到底怎么区分?
📖 《Java 23 种设计模式:从踩坑到精通》开篇:系列介绍与目录 | 上一篇:状态模式 | 当前:策略模式 | 下一篇:模版方法
🔗 返回系列总目录
文章目录
- [Java 23 种设计模式:从踩坑到精通 | 策略模式 ------ 算法族的封装与切换,告别 if-else](#Java 23 种设计模式:从踩坑到精通 | 策略模式 —— 算法族的封装与切换,告别 if-else)
-
- [1. 从"促销活动"的一堆 if-else 说起](#1. 从“促销活动”的一堆 if-else 说起)
-
- [1.1 你的场景该不该用策略模式?](#1.1 你的场景该不该用策略模式?)
- [2. 模式定义与 UML 结构](#2. 模式定义与 UML 结构)
-
- [图文解析(配合上述 UML 图)](#图文解析(配合上述 UML 图))
- [3. 代码实现:电商促销策略](#3. 代码实现:电商促销策略)
-
- [3.1 抽象策略](#3.1 抽象策略)
- [3.2 具体策略](#3.2 具体策略)
- [3.3 上下文:订单结算](#3.3 上下文:订单结算)
- [3.4 客户端](#3.4 客户端)
- [4. Lambda 简化策略模式(Java 8+)](#4. Lambda 简化策略模式(Java 8+))
- [5. 实战案例:电子面单多平台架构中的三层策略](#5. 实战案例:电子面单多平台架构中的三层策略)
-
- [5.1 三层策略接口定义](#5.1 三层策略接口定义)
- [5.2 各平台实现(以奇门和抖音为例)](#5.2 各平台实现(以奇门和抖音为例))
- [5.3 模板编排器(上下文)](#5.3 模板编排器(上下文))
- [5.4 策略模式与工厂模式配合](#5.4 策略模式与工厂模式配合)
- [6. 代码实现:文件排序策略(与 Comparator 对比)](#6. 代码实现:文件排序策略(与 Comparator 对比))
-
- [6.1 抽象策略](#6.1 抽象策略)
- [6.2 具体策略](#6.2 具体策略)
- [6.3 上下文:文件管理器](#6.3 上下文:文件管理器)
- [7. 策略模式 vs 状态模式](#7. 策略模式 vs 状态模式)
- [8. 优缺点一览](#8. 优缺点一览)
- [9. 框架与实践中的应用](#9. 框架与实践中的应用)
-
- [9.1 JDK:Comparator 接口](#9.1 JDK:Comparator 接口)
- [9.2 Spring 资源加载策略](#9.2 Spring 资源加载策略)
- [9.3 支付系统中的支付方式切换](#9.3 支付系统中的支付方式切换)
- [10. 面试必问 + 面试官追问连环炮](#10. 面试必问 + 面试官追问连环炮)
- [11. 六大设计原则在策略模式中的体现](#11. 六大设计原则在策略模式中的体现)
- [🧭 《Java 23 种设计模式:从踩坑到精通》快速导航](#🧭 《Java 23 种设计模式:从踩坑到精通》快速导航)
- [🏭 实战配套:电商多平台电子面单对接实战](#🏭 实战配套:电商多平台电子面单对接实战)
1. 从"促销活动"的一堆 if-else 说起
电商系统经常需要搞促销:满减、打折、立减、赠品...... 每一种促销方式的计算逻辑都不同。如果直接在订单结算方法里写死:
java
double calculate(Order order, String promotionType) {
if ("满减".equals(promotionType)) {
return order.getTotal() - (order.getTotal() / 200) * 50;
} else if ("打折".equals(promotionType)) {
return order.getTotal() * 0.8;
} else if ("立减".equals(promotionType)) {
return order.getTotal() - 30;
}
return order.getTotal();
}
这种方式有明显的痛点:
- 所有算法堆在一起,
calculate方法越来越长; - 新增一种促销就要修改原有代码,违反开闭原则;
- 促销策略无法在运行时动态切换,也无法灵活组合。
策略模式(Strategy Pattern)正是为这种"同一操作、多种算法"的场景而生:它定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
🏭 实战视角 :不只是促销计算,在多平台电子面单系统中,我们也遇到了几乎一模一样的问题------十几个电商平台(奇门、抖音、京东、拼多多......)都需要"取号",但每个平台的API接口、签名算法、请求格式完全不同。如果用
if-else把所有平台的逻辑堆在一起,代码会迅速膨胀到无法维护。我们正是用策略模式解决了这个问题。在本节末尾,我会用这个真实案例来展示策略模式在复杂业务中的落地。
1.1 你的场景该不该用策略模式?
| 判断标准 | 是 → 用策略模式 | 否 → 用其他方式 |
|---|---|---|
| 同一个操作有多种实现方式,且可能动态切换 | ✅ | ❌ |
| 算法逻辑复杂,相互独立,需要避免冗长的条件语句 | ✅ | ❌ |
| 未来需要增加新的算法,且不希望修改原有代码 | ✅ | ❌ |
| 只有两三种固定不变的简单分支 | ❌ | 直接使用 if-else 即可 |
2. 模式定义与 UML 结构
策略模式 定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。它属于 行为型设计模式。

图文解析(配合上述 UML 图)
策略模式的核心角色:
- 抽象策略(
Strategy) :定义所有支持的算法的公共接口,通常只有一个方法如calculate()或algorithm()。 - 具体策略(
ConcreteStrategyA/ConcreteStrategyB):实现具体的算法,每个策略类封装一种独立的算法变体。 - 上下文(
Context):持有策略对象的引用,负责调用策略算法。上下文不关心具体是哪个策略,只需知道如何触发计算。
核心机制:策略模式把"如何做"从"谁要做"中分离出来。上下文对象只知道"我要执行一个算法",但不知道具体是哪个算法。具体算法由客户端在运行时注入。
3. 代码实现:电商促销策略
3.1 抽象策略
java
public interface PromotionStrategy {
double calculate(Order order);
}
💬 白话:所有促销策略都必须能"算钱"。
3.2 具体策略
java
public class FullReductionStrategy implements PromotionStrategy {
@Override
public double calculate(Order order) {
double discount = Math.floor(order.getTotal() / 200) * 50;
System.out.println("应用满减策略:满200减50,减免" + discount + "元");
return order.getTotal() - discount;
}
}
public class DiscountStrategy implements PromotionStrategy {
@Override
public double calculate(Order order) {
System.out.println("应用打折策略:8折");
return order.getTotal() * 0.8;
}
}
public class DirectReductionStrategy implements PromotionStrategy {
@Override
public double calculate(Order order) {
System.out.println("应用立减策略:立减30元");
return order.getTotal() - 30;
}
}
💬 白话:每种促销是一个独立的类,互不干扰,修改满减逻辑不会影响打折。
3.3 上下文:订单结算
java
public class Order {
private double total;
private PromotionStrategy strategy;
public Order(double total) { this.total = total; }
public double getTotal() { return total; }
public void setPromotionStrategy(PromotionStrategy strategy) {
this.strategy = strategy;
}
public double checkout() {
if (strategy == null) {
return total;
}
return strategy.calculate(this);
}
}
💬 白话:订单不关心用哪种促销,它只知道有促销就用促销算钱,没有就原价。
3.4 客户端
java
Order order = new Order(500);
// 使用满减
order.setPromotionStrategy(new FullReductionStrategy());
System.out.println("实付金额:" + order.checkout() + "\n");
// 切换为打折
order.setPromotionStrategy(new DiscountStrategy());
System.out.println("实付金额:" + order.checkout() + "\n");
// 切换为立减
order.setPromotionStrategy(new DirectReductionStrategy());
System.out.println("实付金额:" + order.checkout());
新增一种促销(如"赠品策略"),只需新增一个 GiftStrategy 类,客户端注入即可,订单结算代码零修改。
4. Lambda 简化策略模式(Java 8+)
如果策略接口只有一个抽象方法,那它就是函数式接口,可以用 Lambda 表达式直接替代具体策略类,减少类数量。
java
Order order = new Order(500);
// 满减策略
order.setPromotionStrategy(o -> {
double discount = Math.floor(o.getTotal() / 200) * 50;
return o.getTotal() - discount;
});
// 打折策略
order.setPromotionStrategy(o -> o.getTotal() * 0.8);
System.out.println(order.checkout());
💬 白话:策略逻辑简单时,不需要专门写一个类,Lambda 直接搞定。
5. 实战案例:电子面单多平台架构中的三层策略
理论学完了,来看看策略模式在真实项目中是怎么用的。在我们的多平台电子面单系统中,需要对接十几个电商平台取号。每个平台的API、签名、响应格式完全不同。如果写一堆
if-else分支,代码会迅速膨胀。
我们的做法是:把"取号"这个操作拆成三个策略接口------请求构建、响应解析、异常判断。每个平台(奇门、抖音、京东......)都提供自己的一套实现。
5.1 三层策略接口定义
java
// 请求构建策略:每个平台组装各自的请求
public interface RequestStrategy {
Object buildRequest(WaybillContext ctx);
}
// 响应解析策略:每个平台解析各自的返回
public interface ParseStrategy {
List<TocPickTicketWayBillDetailsNew> parseResponse(WaybillContext ctx, String response);
}
// 异常判断策略:每个平台判断各自的成功/失败
public interface ExceptionStrategy {
boolean isBusinessSuccess(String response);
String extractErrorMsg(String response);
}
5.2 各平台实现(以奇门和抖音为例)
java
// 奇门平台:构建淘宝SDK请求
public class QiMenRequestStrategy implements RequestStrategy {
@Override
public Object buildRequest(WaybillContext ctx) {
return QiMenWaybillBuilder.buildRequest(ctx);
}
}
// 抖音平台:构建HTTP JSON请求
public class DouYinRequestStrategy implements RequestStrategy {
@Override
public Object buildRequest(WaybillContext ctx) {
return DouYinWaybillBuilder.buildRequest(ctx);
}
}
5.3 模板编排器(上下文)
java
public class WaybillFetchTemplate {
private final RequestStrategy requestStrategy;
private final ParseStrategy parseStrategy;
private final ExceptionStrategy exceptionStrategy;
// 通过构造函数注入三个策略
public WaybillFetchTemplate(RequestStrategy req, ParseStrategy parse, ExceptionStrategy ex) {
this.requestStrategy = req;
this.parseStrategy = parse;
this.exceptionStrategy = ex;
}
public boolean execute(WaybillContext ctx) {
Object request = requestStrategy.buildRequest(ctx); // 策略:构建请求
String response = apiInvoker.invoke(ctx, request); // 调用API
if (!exceptionStrategy.isBusinessSuccess(response)) { // 策略:判断成功
markException(ctx.getTicket(), exceptionStrategy.extractErrorMsg(response));
return false;
}
List<Detail> details = parseStrategy.parseResponse(ctx, response); // 策略:解析响应
persistence.saveAndBind(ctx.getTicket(), details);
return true;
}
}
💡 关键设计 :
WaybillFetchTemplate只依赖接口,不关心是奇门还是抖音。新增一个平台(比如快手),只需新增三个策略实现类并注册到工厂,模板编排器代码零改动。这就是策略模式带来的开闭原则实践。
5.4 策略模式与工厂模式配合
策略模式解决了"怎么换"的问题,但"换哪个"通常需要工厂模式来配合。在我们的架构中,StrategyFactory 根据平台编码返回对应的策略实例:
java
// 根据平台编码获取策略
RequestStrategy req = strategyFactory.getRequestStrategy(platformCode, original);
ParseStrategy parse = strategyFactory.getParseStrategy(platformCode, original);
ExceptionStrategy ex = strategyFactory.getExceptionStrategy(platformCode, original);
// 注入到模板中
WaybillFetchTemplate template = new WaybillFetchTemplate(req, parse, ex);
template.execute(ctx);
📖 这套架构的完整设计、复合Key路由机制、以及如何用策略模式+工厂模式配合实现"新增平台零改动核心代码",详见电子面单实战系列的《多平台统一架构设计》和《策略工厂复合Key路由改造》。
6. 代码实现:文件排序策略(与 Comparator 对比)
文件管理器中,用户可以按名称、大小、修改日期排序文件。这天然适合策略模式,而且 JDK 的 Comparator 本身就是策略模式的经典实现。
6.1 抽象策略
java
public interface SortStrategy {
void sort(List<FileInfo> files);
}
6.2 具体策略
java
public class SortByName implements SortStrategy {
@Override
public void sort(List<FileInfo> files) {
files.sort(Comparator.comparing(FileInfo::getName));
System.out.println("按名称排序完成");
}
}
public class SortBySize implements SortStrategy {
@Override
public void sort(List<FileInfo> files) {
files.sort(Comparator.comparingLong(FileInfo::getSize));
System.out.println("按大小排序完成");
}
}
public class SortByDate implements SortStrategy {
@Override
public void sort(List<FileInfo> files) {
files.sort(Comparator.comparing(FileInfo::getModifiedDate));
System.out.println("按修改日期排序完成");
}
}
💬 白话 :
Comparator本身就是策略接口,comparing()方法返回的就是具体策略。
6.3 上下文:文件管理器
java
public class FileManager {
private SortStrategy sortStrategy;
public void setSortStrategy(SortStrategy sortStrategy) {
this.sortStrategy = sortStrategy;
}
public void sortFiles(List<FileInfo> files) {
if (sortStrategy != null) {
sortStrategy.sort(files);
}
}
}
7. 策略模式 vs 状态模式
这是面试中极容易混淆的一对:
| 对比维度 | 策略模式 | 状态模式 |
|---|---|---|
| 目的 | 替换算法 | 根据内部状态自动改变行为 |
| 谁决定切换 | 客户端显式选择策略 | 状态类自己决定何时切换到另一个状态 |
| 关注点 | 算法的替换与组合 | 状态之间的流转和转换 |
| 上下文感知 | 策略通常不知道上下文的存在 | 状态知道上下文的存在,并可能调用上下文方法 |
| 典型应用 | Comparator、支付方式、折扣策略 |
订单状态机、线程状态、工单流转 |
💡 简单记忆:策略模式是"主动选择算法",状态模式是"被动切换行为"。策略的切换由客户端决定,状态的切换由状态对象自己决定。
8. 优缺点一览
| 优点 | 缺点 |
|---|---|
| 开闭原则:新增策略无需修改上下文,只需新增策略类 | 类数量增加:每个策略一个类,策略多时类膨胀 |
消除条件判断 :避免了大量 if-else 或 switch |
客户端必须知道策略差异:需要了解各策略特点才能正确选择 |
| 策略可动态切换:运行时根据条件替换策略 | 策略间通信成本:如果策略需要与上下文复杂交互,需额外设计 |
| 代码复用:策略类独立,可被多个上下文复用 | 增加对象数量:每个策略都是一个对象,可能增加内存开销 |
9. 框架与实践中的应用
9.1 JDK:Comparator 接口
java.util.Comparator 是策略模式的教科书级应用。通过传入不同的 Comparator 实现,TreeSet、Collections.sort() 等方法可以在不修改数据结构的情况下改变排序规则。
java
List<String> list = Arrays.asList("apple", "banana", "cherry");
list.sort(Comparator.naturalOrder()); // 正序
list.sort(Comparator.reverseOrder()); // 倒序
9.2 Spring 资源加载策略
Spring 的 ResourceLoader 可以根据资源路径前缀(如 classpath:、file:)选择不同的资源加载策略,本质上是策略模式。
9.3 支付系统中的支付方式切换
电商平台支持支付宝、微信、银行卡等多种支付方式,每种支付方式封装为一个策略,用户在结算时选择,系统在运行时动态调用对应策略。
10. 面试必问 + 面试官追问连环炮
基础必问
- 策略模式与状态模式的区别? → 策略由客户端决定切换,状态由状态类自己决定转换。
- JDK 中哪里用了策略模式? →
Comparator、Thread的UncaughtExceptionHandler。 - 策略模式如何消除 if-else? → 将每个分支算法封装为独立的策略类,通过多态调用,客户端只需注入当前策略。
面试官追问
- "策略模式会导致类膨胀,怎么解决?"
👉 对于逻辑简单的策略,使用 Lambda 表达式或函数式接口直接内联,避免创建独立类。 - "策略模式和命令模式有什么区别?"
👉 策略关注算法的封装与替换,命令关注请求的封装与调用者解耦。命令通常支持撤销,策略不关心。 - "你在项目中用过策略模式吗?能举个具体的例子吗?"
👉 可以讲电子面单的三层策略设计------为什么拆分、怎么配合工厂模式、新增平台零改动核心流程。这个案例既有代码细节又有架构高度,比背概念强十倍。完整的架构设计见电子面单实战系列《多平台统一架构设计》。
🎉 恭喜 :如果你能立刻说出
Comparator是策略模式,并清楚策略与状态的本质区别,还能用电子面单案例回答"项目里怎么用的",你已经掌握了行为型模式中最常用的"算法替换"设计。
11. 六大设计原则在策略模式中的体现
| 设计原则 | 在策略模式中的体现 |
|---|---|
| 单一职责原则(SRP) | 每个策略类只负责一种算法实现 |
| 开闭原则(OCP) | 新增策略无需修改上下文和原有策略,只需新增策略类 |
| 里氏替换原则(LSP) | 所有策略类都实现 Strategy 接口,可无缝替换 |
| 依赖倒置原则(DIP) | 上下文依赖抽象 Strategy 接口,不依赖具体策略 |
| 接口隔离原则(ISP) | Strategy 接口只定义算法方法,精简无冗余 |
| 迪米特法则(LoD) | 上下文只知道策略接口,不了解策略内部实现 |
🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- [上一篇:状态模式 ------ if-else 满天飞?让状态自己决定行为](#上一篇:状态模式 —— if-else 满天飞?让状态自己决定行为)
- 当前:策略模式 ------ 算法族的封装与切换,告别 if-else(你在这里)
- [下一篇:模版方法 ------ 定义算法骨架,交给子类填充细节](#下一篇:模版方法 —— 定义算法骨架,交给子类填充细节) 🚧 即将发布
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理......
- 行为型模式汇总:观察者、策略、模板方法......
🏭 实战配套:电商多平台电子面单对接实战
本文第5节展示的电子面单三层策略架构,在我们的电子面单实战系列中有完整的落地代码和设计演进过程。如果你对以下问题感兴趣,推荐延伸阅读:
- 策略模式 + 工厂模式:如何配合实现复合Key路由,解决同一平台下多子渠道的策略选择?
- 策略模式 + 模板方法模式:编排器如何用组合方式固定流程骨架,策略只填差异步骤?
- 新增平台零改动核心代码:如何实现真正的开闭原则?
📖 《电商多平台电子面单对接实战》
- 系列开篇:从"能跑就行"到"整洁架构"
- 多平台统一架构设计 ------ 编排器+策略模式的完整落地
- 策略工厂复合Key路由改造 ------ 策略模式与工厂模式的配合实战
💡 学习建议:设计模式系列讲"为什么这么用",电子面单系列讲"怎么用"。两者搭配,理论+实战闭环。
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。📦 福利预告 :全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇:模板方法模式:定义算法骨架,交给子类填充细节!🚧 即将发布,敬请关注!
📌 除了设计模式,我也在深挖智能物流实战 (WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。