Java 23 种设计模式:从踩坑到精通 | 策略模式 —— 算法族的封装与切换,告别 if-else
Java 23 种设计模式从踩坑到精通 | 策略模式 —— 算法族的封装与切换告别 if-else摘要当同一个操作有多种算法实现且需要根据场景灵活切换时if-else或switch会将所有算法混杂在一起导致逻辑臃肿、扩展困难。策略模式将每个算法封装为独立的策略类使它们可以相互替换且算法的变化不影响使用算法的客户端。本文从电商促销策略的场景出发完整讲解策略模式的原理、UML、代码实现、与状态模式的区别并结合电子面单多平台架构实战、JDK 排序比较器、Spring 资源加载等应用帮你掌握“算法对象化”的设计精髓。️本文阅读地图3 分钟速览为什么一坨if-else促销代码一定会炸策略模式三大角色拆解手写促销策略引擎满减、打折、立减Lambda 简化策略模式实战案例电子面单多平台架构中的三层策略应用JDKComparator/ SpringResourceLoader如何体现面试必问策略 vs 状态到底怎么区分《Java 23 种设计模式从踩坑到精通》开篇系列介绍与目录 上一篇状态模式 当前策略模式 下一篇模版方法 返回系列总目录文章目录Java 23 种设计模式从踩坑到精通 | 策略模式 —— 算法族的封装与切换告别 if-else1. 从“促销活动”的一堆 if-else 说起1.1 你的场景该不该用策略模式2. 模式定义与 UML 结构图文解析配合上述 UML 图3. 代码实现电商促销策略3.1 抽象策略3.2 具体策略3.3 上下文订单结算3.4 客户端4. Lambda 简化策略模式Java 85. 实战案例电子面单多平台架构中的三层策略5.1 三层策略接口定义5.2 各平台实现以奇门和抖音为例5.3 模板编排器上下文5.4 策略模式与工厂模式配合6. 代码实现文件排序策略与 Comparator 对比6.1 抽象策略6.2 具体策略6.3 上下文文件管理器7. 策略模式 vs 状态模式8. 优缺点一览9. 框架与实践中的应用9.1 JDKComparator 接口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){doublediscountMath.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.totaltotal;}publicdoublegetTotal(){returntotal;}publicvoidsetPromotionStrategy(PromotionStrategystrategy){this.strategystrategy;}publicdoublecheckout(){if(strategynull){returntotal;}returnstrategy.calculate(this);}}白话订单不关心用哪种促销它只知道有促销就用促销算钱没有就原价。3.4 客户端OrderordernewOrder(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 表达式直接替代具体策略类减少类数量。OrderordernewOrder(500);// 满减策略order.setPromotionStrategy(o-{doublediscountMath.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{ListTocPickTicketWayBillDetailsNewparseResponse(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.requestStrategyreq;this.parseStrategyparse;this.exceptionStrategyex;}publicbooleanexecute(WaybillContextctx){ObjectrequestrequestStrategy.buildRequest(ctx);// 策略构建请求StringresponseapiInvoker.invoke(ctx,request);// 调用APIif(!exceptionStrategy.isBusinessSuccess(response)){// 策略判断成功markException(ctx.getTicket(),exceptionStrategy.extractErrorMsg(response));returnfalse;}ListDetaildetailsparseStrategy.parseResponse(ctx,response);// 策略解析响应persistence.saveAndBind(ctx.getTicket(),details);returntrue;}}关键设计WaybillFetchTemplate只依赖接口不关心是奇门还是抖音。新增一个平台比如快手只需新增三个策略实现类并注册到工厂模板编排器代码零改动。这就是策略模式带来的开闭原则实践。5.4 策略模式与工厂模式配合策略模式解决了“怎么换”的问题但“换哪个”通常需要工厂模式来配合。在我们的架构中StrategyFactory根据平台编码返回对应的策略实例// 根据平台编码获取策略RequestStrategyreqstrategyFactory.getRequestStrategy(platformCode,original);ParseStrategyparsestrategyFactory.getParseStrategy(platformCode,original);ExceptionStrategyexstrategyFactory.getExceptionStrategy(platformCode,original);// 注入到模板中WaybillFetchTemplatetemplatenewWaybillFetchTemplate(req,parse,ex);template.execute(ctx); 这套架构的完整设计、复合Key路由机制、以及如何用策略模式工厂模式配合实现“新增平台零改动核心代码”详见电子面单实战系列的《多平台统一架构设计》和《策略工厂复合Key路由改造》。6. 代码实现文件排序策略与 Comparator 对比文件管理器中用户可以按名称、大小、修改日期排序文件。这天然适合策略模式而且 JDK 的Comparator本身就是策略模式的经典实现。6.1 抽象策略publicinterfaceSortStrategy{voidsort(ListFileInfofiles);}6.2 具体策略publicclassSortByNameimplementsSortStrategy{Overridepublicvoidsort(ListFileInfofiles){files.sort(Comparator.comparing(FileInfo::getName));System.out.println(按名称排序完成);}}publicclassSortBySizeimplementsSortStrategy{Overridepublicvoidsort(ListFileInfofiles){files.sort(Comparator.comparingLong(FileInfo::getSize));System.out.println(按大小排序完成);}}publicclassSortByDateimplementsSortStrategy{Overridepublicvoidsort(ListFileInfofiles){files.sort(Comparator.comparing(FileInfo::getModifiedDate));System.out.println(按修改日期排序完成);}}白话Comparator本身就是策略接口comparing()方法返回的就是具体策略。6.3 上下文文件管理器publicclassFileManager{privateSortStrategysortStrategy;publicvoidsetSortStrategy(SortStrategysortStrategy){this.sortStrategysortStrategy;}publicvoidsortFiles(ListFileInfofiles){if(sortStrategy!null){sortStrategy.sort(files);}}}7. 策略模式 vs 状态模式这是面试中极容易混淆的一对对比维度策略模式状态模式目的替换算法根据内部状态自动改变行为谁决定切换客户端显式选择策略状态类自己决定何时切换到另一个状态关注点算法的替换与组合状态之间的流转和转换上下文感知策略通常不知道上下文的存在状态知道上下文的存在并可能调用上下文方法典型应用Comparator、支付方式、折扣策略订单状态机、线程状态、工单流转简单记忆策略模式是“主动选择算法”状态模式是“被动切换行为”。策略的切换由客户端决定状态的切换由状态对象自己决定。8. 优缺点一览优点缺点开闭原则新增策略无需修改上下文只需新增策略类类数量增加每个策略一个类策略多时类膨胀消除条件判断避免了大量if-else或switch客户端必须知道策略差异需要了解各策略特点才能正确选择策略可动态切换运行时根据条件替换策略策略间通信成本如果策略需要与上下文复杂交互需额外设计代码复用策略类独立可被多个上下文复用增加对象数量每个策略都是一个对象可能增加内存开销9. 框架与实践中的应用9.1 JDKComparator 接口java.util.Comparator是策略模式的教科书级应用。通过传入不同的Comparator实现TreeSet、Collections.sort()等方法可以在不修改数据结构的情况下改变排序规则。ListStringlistArrays.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接口不依赖具体策略接口隔离原则ISPStrategy接口只定义算法方法精简无冗余迪米特法则LoD上下文只知道策略接口不了解策略内部实现 《Java 23 种设计模式从踩坑到精通》快速导航开篇系列介绍与目录上一篇状态模式 —— if-else 满天飞让状态自己决定行为当前策略模式 —— 算法族的封装与切换告别 if-else你在这里下一篇模版方法 —— 定义算法骨架交给子类填充细节 即将发布创建型模式汇总单例、工厂、建造者、原型结构型模式汇总适配器、装饰器、代理……行为型模式汇总观察者、策略、模板方法…… 实战配套电商多平台电子面单对接实战本文第5节展示的电子面单三层策略架构在我们的电子面单实战系列中有完整的落地代码和设计演进过程。如果你对以下问题感兴趣推荐延伸阅读策略模式 工厂模式如何配合实现复合Key路由解决同一平台下多子渠道的策略选择策略模式 模板方法模式编排器如何用组合方式固定流程骨架策略只填差异步骤新增平台零改动核心代码如何实现真正的开闭原则《电商多平台电子面单对接实战》系列开篇从“能跑就行”到“整洁架构”多平台统一架构设计 —— 编排器策略模式的完整落地策略工厂复合Key路由改造 —— 策略模式与工厂模式的配合实战学习建议设计模式系列讲“为什么这么用”电子面单系列讲“怎么用”。两者搭配理论实战闭环。 关注《Java 23 种设计模式从踩坑到精通》用 25 篇文章彻底吃透设计模式。福利预告全系列代码及 UML 源码将在完结时统一打包开放点击「关注」「收藏」第一时间获取。下一篇模板方法模式定义算法骨架交给子类填充细节 即将发布敬请关注 除了设计模式我也在深挖智能物流实战WMS、托盘调度、机器学习落地。欢迎点击头像看看专栏 《出版社物流WMS智能调度实战》。技术相通思路可鉴。