Java 23 种设计模式:从踩坑到精通 | 策略模式 —— 算法族的封装与切换,告别 if-else
Java 23 种设计模式:从踩坑到精通 | 策略模式 —— 算法族的封装与切换,告别 if-else
摘要:当同一个操作有多种算法实现,且需要根据场景灵活切换时,
if-else或switch会将所有算法混杂在一起,导致逻辑臃肿、扩展困难。策略模式将每个算法封装为独立的策略类,使它们可以相互替换,且算法的变化不影响使用算法的客户端。本文从电商促销策略的场景出发,完整讲解策略模式的原理、UML、代码实现、与状态模式的区别,并结合电子面单多平台架构实战、JDK 排序比较器、Spring 资源加载等应用,帮你掌握“算法对象化”的设计精髓。
🗺️本文阅读地图(3 分钟速览)
- 为什么一坨
if-else促销代码一定会炸?- 策略模式三大角色拆解
- 手写促销策略引擎(满减、打折、立减)
- Lambda 简化策略模式
- 实战案例:电子面单多平台架构中的三层策略应用
- JDK
Comparator/ SpringResourceLoader如何体现- 面试必问:策略 vs 状态,到底怎么区分?
📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:状态模式 |当前:策略模式| 下一篇:模版方法
🔗 返回系列总目录
文章目录
- Java 23 种设计模式:从踩坑到精通 | 策略模式 —— 算法族的封装与切换,告别 if-else
- 1. 从“促销活动”的一堆 if-else 说起
- 1.1 你的场景该不该用策略模式?
- 2. 模式定义与 UML 结构
- 图文解析(配合上述 UML 图)
- 3. 代码实现:电商促销策略
- 3.1 抽象策略
- 3.2 具体策略
- 3.3 上下文:订单结算
- 3.4 客户端
- 4. Lambda 简化策略模式(Java 8+)
- 5. 实战案例:电子面单多平台架构中的三层策略
- 5.1 三层策略接口定义
- 5.2 各平台实现(以奇门和抖音为例)
- 5.3 模板编排器(上下文)
- 5.4 策略模式与工厂模式配合
- 6. 代码实现:文件排序策略(与 Comparator 对比)
- 6.1 抽象策略
- 6.2 具体策略
- 6.3 上下文:文件管理器
- 7. 策略模式 vs 状态模式
- 8. 优缺点一览
- 9. 框架与实践中的应用
- 9.1 JDK:Comparator 接口
- 9.2 Spring 资源加载策略
- 9.3 支付系统中的支付方式切换
- 10. 面试必问 + 面试官追问连环炮
- 11. 六大设计原则在策略模式中的体现
- 🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 🏭 实战配套:电商多平台电子面单对接实战
1. 从“促销活动”的一堆 if-else 说起
电商系统经常需要搞促销:满减、打折、立减、赠品…… 每一种促销方式的计算逻辑都不同。如果直接在订单结算方法里写死:
doublecalculate(Orderorder,StringpromotionType){if("满减".equals(promotionType)){returnorder.getTotal()-(order.getTotal()/200)*50;}elseif("打折".equals(promotionType)){returnorder.getTotal()*0.8;}elseif("立减".equals(promotionType)){returnorder.getTotal()-30;}returnorder.getTotal();}这种方式有明显的痛点:
- 所有算法堆在一起,
calculate方法越来越长; - 新增一种促销就要修改原有代码,违反开闭原则;
- 促销策略无法在运行时动态切换,也无法灵活组合。
策略模式(Strategy Pattern)正是为这种“同一操作、多种算法”的场景而生:它定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
🏭实战视角:不只是促销计算,在多平台电子面单系统中,我们也遇到了几乎一模一样的问题——十几个电商平台(奇门、抖音、京东、拼多多……)都需要“取号”,但每个平台的API接口、签名算法、请求格式完全不同。如果用
if-else把所有平台的逻辑堆在一起,代码会迅速膨胀到无法维护。我们正是用策略模式解决了这个问题。在本节末尾,我会用这个真实案例来展示策略模式在复杂业务中的落地。
1.1 你的场景该不该用策略模式?
| 判断标准 | 是 → 用策略模式 | 否 → 用其他方式 |
|---|---|---|
| 同一个操作有多种实现方式,且可能动态切换 | ✅ | ❌ |
| 算法逻辑复杂,相互独立,需要避免冗长的条件语句 | ✅ | ❌ |
| 未来需要增加新的算法,且不希望修改原有代码 | ✅ | ❌ |
| 只有两三种固定不变的简单分支 | ❌ | 直接使用if-else即可 |
2. 模式定义与 UML 结构
策略模式定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。它属于行为型设计模式。
图文解析(配合上述 UML 图)
策略模式的核心角色:
- 抽象策略(
Strategy):定义所有支持的算法的公共接口,通常只有一个方法如calculate()或algorithm()。 - 具体策略(
ConcreteStrategyA/ConcreteStrategyB):实现具体的算法,每个策略类封装一种独立的算法变体。 - 上下文(
Context):持有策略对象的引用,负责调用策略算法。上下文不关心具体是哪个策略,只需知道如何触发计算。
核心机制:策略模式把“如何做”从“谁要做”中分离出来。上下文对象只知道“我要执行一个算法”,但不知道具体是哪个算法。具体算法由客户端在运行时注入。
3. 代码实现:电商促销策略
3.1 抽象策略
publicinterfacePromotionStrategy{doublecalculate(Orderorder);}💬白话:所有促销策略都必须能“算钱”。
3.2 具体策略
publicclassFullReductionStrategyimplementsPromotionStrategy{@Overridepublicdoublecalculate(Orderorder){doublediscount=Math.floor(order.getTotal()/200)*50;System.out.println("应用满减策略:满200减50,减免"+discount+"元");returnorder.getTotal()-discount;}}publicclassDiscountStrategyimplementsPromotionStrategy{@Overridepublicdoublecalculate(Orderorder){System.out.println("应用打折策略:8折");returnorder.getTotal()*0.8;}}publicclassDirectReductionStrategyimplementsPromotionStrategy{@Overridepublicdoublecalculate(Orderorder){System.out.println("应用立减策略:立减30元");returnorder.getTotal()-30;}}💬白话:每种促销是一个独立的类,互不干扰,修改满减逻辑不会影响打折。
3.3 上下文:订单结算
publicclassOrder{privatedoubletotal;privatePromotionStrategystrategy;publicOrder(doubletotal){this.total=total;}publicdoublegetTotal(){returntotal;}publicvoidsetPromotionStrategy(PromotionStrategystrategy){this.strategy=strategy;}publicdoublecheckout(){if(strategy==null){returntotal;}returnstrategy.calculate(this);}}💬白话:订单不关心用哪种促销,它只知道有促销就用促销算钱,没有就原价。
3.4 客户端
Orderorder=newOrder(500);// 使用满减order.setPromotionStrategy(newFullReductionStrategy());System.out.println("实付金额:"+order.checkout()+"\n");// 切换为打折order.setPromotionStrategy(newDiscountStrategy());System.out.println("实付金额:"+order.checkout()+"\n");// 切换为立减order.setPromotionStrategy(newDirectReductionStrategy());System.out.println("实付金额:"+order.checkout());新增一种促销(如“赠品策略”),只需新增一个GiftStrategy类,客户端注入即可,订单结算代码零修改。
4. Lambda 简化策略模式(Java 8+)
如果策略接口只有一个抽象方法,那它就是函数式接口,可以用 Lambda 表达式直接替代具体策略类,减少类数量。
Orderorder=newOrder(500);// 满减策略order.setPromotionStrategy(o->{doublediscount=Math.floor(o.getTotal()/200)*50;returno.getTotal()-discount;});// 打折策略order.setPromotionStrategy(o->o.getTotal()*0.8);System.out.println(order.checkout());💬白话:策略逻辑简单时,不需要专门写一个类,Lambda 直接搞定。
5. 实战案例:电子面单多平台架构中的三层策略
理论学完了,来看看策略模式在真实项目中是怎么用的。在我们的多平台电子面单系统中,需要对接十几个电商平台取号。每个平台的API、签名、响应格式完全不同。如果写一堆
if-else分支,代码会迅速膨胀。
我们的做法是:把“取号”这个操作拆成三个策略接口——请求构建、响应解析、异常判断。每个平台(奇门、抖音、京东……)都提供自己的一套实现。
5.1 三层策略接口定义
// 请求构建策略:每个平台组装各自的请求publicinterfaceRequestStrategy{ObjectbuildRequest(WaybillContextctx);}// 响应解析策略:每个平台解析各自的返回publicinterfaceParseStrategy{List<TocPickTicketWayBillDetailsNew>parseResponse(WaybillContextctx,Stringresponse);}// 异常判断策略:每个平台判断各自的成功/失败publicinterfaceExceptionStrategy{booleanisBusinessSuccess(Stringresponse);StringextractErrorMsg(Stringresponse);}5.2 各平台实现(以奇门和抖音为例)
// 奇门平台:构建淘宝SDK请求publicclassQiMenRequestStrategyimplementsRequestStrategy{@OverridepublicObjectbuildRequest(WaybillContextctx){returnQiMenWaybillBuilder.buildRequest(ctx);}}// 抖音平台:构建HTTP JSON请求publicclassDouYinRequestStrategyimplementsRequestStrategy{@OverridepublicObjectbuildRequest(WaybillContextctx){returnDouYinWaybillBuilder.buildRequest(ctx);}}5.3 模板编排器(上下文)
publicclassWaybillFetchTemplate{privatefinalRequestStrategyrequestStrategy;privatefinalParseStrategyparseStrategy;privatefinalExceptionStrategyexceptionStrategy;// 通过构造函数注入三个策略publicWaybillFetchTemplate(RequestStrategyreq,ParseStrategyparse,ExceptionStrategyex){this.requestStrategy=req;this.parseStrategy=parse;this.exceptionStrategy=ex;}publicbooleanexecute(WaybillContextctx){Objectrequest=requestStrategy.buildRequest(ctx);// 策略:构建请求Stringresponse=apiInvoker.invoke(ctx,request);// 调用APIif(!exceptionStrategy.isBusinessSuccess(response)){// 策略:判断成功markException(ctx.getTicket(),exceptionStrategy.extractErrorMsg(response));returnfalse;}List<Detail>details=parseStrategy.parseResponse(ctx,response);// 策略:解析响应persistence.saveAndBind(ctx.getTicket(),details);returntrue;}}💡关键设计:
WaybillFetchTemplate只依赖接口,不关心是奇门还是抖音。新增一个平台(比如快手),只需新增三个策略实现类并注册到工厂,模板编排器代码零改动。这就是策略模式带来的开闭原则实践。
5.4 策略模式与工厂模式配合
策略模式解决了“怎么换”的问题,但“换哪个”通常需要工厂模式来配合。在我们的架构中,StrategyFactory根据平台编码返回对应的策略实例:
// 根据平台编码获取策略RequestStrategyreq=strategyFactory.getRequestStrategy(platformCode,original);ParseStrategyparse=strategyFactory.getParseStrategy(platformCode,original);ExceptionStrategyex=strategyFactory.getExceptionStrategy(platformCode,original);// 注入到模板中WaybillFetchTemplatetemplate=newWaybillFetchTemplate(req,parse,ex);template.execute(ctx);📖 这套架构的完整设计、复合Key路由机制、以及如何用策略模式+工厂模式配合实现“新增平台零改动核心代码”,详见电子面单实战系列的《多平台统一架构设计》和《策略工厂复合Key路由改造》。
6. 代码实现:文件排序策略(与 Comparator 对比)
文件管理器中,用户可以按名称、大小、修改日期排序文件。这天然适合策略模式,而且 JDK 的Comparator本身就是策略模式的经典实现。
6.1 抽象策略
publicinterfaceSortStrategy{voidsort(List<FileInfo>files);}6.2 具体策略
publicclassSortByNameimplementsSortStrategy{@Overridepublicvoidsort(List<FileInfo>files){files.sort(Comparator.comparing(FileInfo::getName));System.out.println("按名称排序完成");}}publicclassSortBySizeimplementsSortStrategy{@Overridepublicvoidsort(List<FileInfo>files){files.sort(Comparator.comparingLong(FileInfo::getSize));System.out.println("按大小排序完成");}}publicclassSortByDateimplementsSortStrategy{@Overridepublicvoidsort(List<FileInfo>files){files.sort(Comparator.comparing(FileInfo::getModifiedDate));System.out.println("按修改日期排序完成");}}💬白话:
Comparator本身就是策略接口,comparing()方法返回的就是具体策略。
6.3 上下文:文件管理器
publicclassFileManager{privateSortStrategysortStrategy;publicvoidsetSortStrategy(SortStrategysortStrategy){this.sortStrategy=sortStrategy;}publicvoidsortFiles(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()等方法可以在不修改数据结构的情况下改变排序规则。
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(你在这里)
- 下一篇:模版方法 —— 定义算法骨架,交给子类填充细节 🚧 即将发布
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理……
- 行为型模式汇总:观察者、策略、模板方法……
🏭 实战配套:电商多平台电子面单对接实战
本文第5节展示的电子面单三层策略架构,在我们的电子面单实战系列中有完整的落地代码和设计演进过程。如果你对以下问题感兴趣,推荐延伸阅读:
- 策略模式 + 工厂模式:如何配合实现复合Key路由,解决同一平台下多子渠道的策略选择?
- 策略模式 + 模板方法模式:编排器如何用组合方式固定流程骨架,策略只填差异步骤?
- 新增平台零改动核心代码:如何实现真正的开闭原则?
📖《电商多平台电子面单对接实战》
- 系列开篇:从“能跑就行”到“整洁架构”
- 多平台统一架构设计 —— 编排器+策略模式的完整落地
- 策略工厂复合Key路由改造 —— 策略模式与工厂模式的配合实战
💡学习建议:设计模式系列讲“为什么这么用”,电子面单系列讲“怎么用”。两者搭配,理论+实战闭环。
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀下一篇:模板方法模式:定义算法骨架,交给子类填充细节!🚧 即将发布,敬请关注!
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。
