news 2026/10/22 3:09:08

策略模式实战:优雅消除if-else,Spring落地全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
策略模式实战:优雅消除if-else,Spring落地全攻略

1. 从一段“烂代码”说起

先说个我早期做项目时遇到的真实场景。某个同事负责一个后端接口,里面有一段“经典”的业务逻辑:根据用户类型发放不同力度的优惠券。代码长这样:

if ("new_user".equals(userType)) { // 新用户注册礼包,满99减30 } else if ("vip".equals(userType)) { // VIP会员专享,满199减80 } else if ("normal".equals(userType)) { // 普通用户,满299减20 } else if ("invited".equals(userType)) { // 被邀请用户,满99减10 } else { // 默认,无优惠 }

你可能觉得这没啥问题,但实际情况是:业务方隔三差五加一种用户类型,每次都要往这个方法里塞一段if。过了两三个月,这个方法膨胀到一百多行,看着就头大。更要命的是,测试同事每次都要重新把整个方法的逻辑过一遍,生怕新增分支影响老逻辑。项目组里的每个人都知道这代码“有病”,但没人愿意动,因为太容易改坏了。

这就是典型的“面向过程式分支”写法,它的问题不在于if本身,而在于它把“判断”和“执行”绑死在了一起。每增加一种策略,就要修改旧代码,这违反了设计原则里非常重要的一条——开闭原则:对扩展开放,对修改关闭。

解药就是标题里说的这个老朋友:策略模式。这篇文章我会用最朴素的语言,从概念讲到实战,再附上我在项目里踩过的坑和总结的一些代码写法上的习惯,希望能帮你彻底把它用起来,而不是停留在背定义、画 UML 图的表面阶段。

这篇文章非常适合三类读者:

  • 刚接触设计模式、对策略模式只停留在“好像听过这个名词”的初学者;
  • 已经能看懂策略模式 Demo,但不知道在 Spring 项目里怎么落地的新人;
  • 被一堆if-else缠身、想重构又不敢动手的开发者。

2. 策略模式到底在说什么

2.1 一个生活化的类比

咱们先不聊代码,聊个生活场景。

想象你开了一家饺子店,店里的厨师分工明确:张三擅长擀皮,李四擅长调馅,王五擅长煮饺子。顾客下单后,店长不会把所有活儿都自己干,而是根据订单内容,把任务分配给对应的师傅。

这里每个师傅就是一种“策略”,店长只要做到“调度”,他自己不需要会擀皮,也不需要会调馅。以后如果来了个赵六,擅长煎饺子,店长只需要新增一个岗位,不用把全店的工作流程改一遍。

策略模式的核心思想就是这么简单:把一组可互相替换的算法(或业务规则)封装起来,客户端通过一个统一的入口去调用,至于具体用哪个实现,在运行时决定。

2.2 三个核心角色

陌生名词最容易劝退小白。咱们先记三个角色,一切代码都只是这三个角色的具象化。

角色职责类比
策略接口(Strategy)定义一组算法的统一规范,通常是一个接口饺子店里的“厨师”岗位说明书
具体策略(Concrete Strategy)实现策略接口,各自完成一种具体算法张三、李四、王五
上下文(Context)持有策略接口的引用,对外提供统一的调用入口店长,负责调派人手

很多教材里还会提到“客户端”(Client),它负责创建具体策略对象并设置到上下文中。在 Spring 项目里,通常由容器帮我们完成这部分工作。

2.3 和状态模式的区别

很多初学者会把策略模式和状态模式搞混,因为它们的类图长得有点像。我在这里简单区分一下,后面不再赘述:

  • 策略模式的“策略”是由客户端主动选择的,客户端清楚知道自己要什么规则;
  • 状态模式的“状态”是对象内部自己流转的,外部并不关心对象当前处于什么状态,只关心它对外表现的行为。

打个比方:策略模式像你用导航软件选“最快路线”还是“少收费路线”,是你决定的;状态模式像手机根据你当前所处位置自动切换“响铃”“振动”“静音”,是设备自己判断的。

这个区别在实际项目中很重要,因为一旦选错模式,代码会变得非常别扭。这一点我会在本文最后的实操心得里再展开讲一讲。

3. 手写一个最最简单的 Demo

“Talk is cheap.” 咱们直接上手写代码。这个 Demo 不依赖任何框架,只用 JDK 自带的能力,保证你能独立跑通。

3.1 定义策略接口

目标是实现一个计算器,支持加法、减法、乘法。先定义一个统一的策略接口:

public interface CalculateStrategy { int calculate(int a, int b); }

就这么简单。这个接口就是整个策略模式的核心契约:你输入两个数,我还你一个数。

3.2 写三个具体策略

加法策略:

public class AddStrategy implements CalculateStrategy { @Override public int calculate(int a, int b) { return a + b; } }

减法策略:

public class SubtractStrategy implements CalculateStrategy { @Override public int calculate(int a, int b) { return a - b; } }

乘法策略:

public class MultiplyStrategy implements CalculateStrategy { @Override public int calculate(int a, int b) { return a * b; } }

每个策略类只做一件事,所以它们都非常短。这不是偷懒,这正是策略模式的设计目标:每个策略足够内聚,单一职责。将来要增加除法,直接新增一个 DivideStrategy 类,不影响任何旧代码。

3.3 写上下文

上下文是为了屏蔽客户端与具体策略实现类的耦合。没有上下文,调用方就得自己 new 一个策略类,然后直接调calculate,这也不是不行,但调用方就和具体实现绑定了,失去了策略“可替换”的意义。

public class CalculatorContext { private CalculateStrategy strategy; // 通过构造方法注入策略 public CalculatorContext(CalculateStrategy strategy) { this.strategy = strategy; } // 通过 setter 方法注入策略(可实现运行时动态切换) public void setStrategy(CalculateStrategy strategy) { this.strategy = strategy; } public int execute(int a, int b) { return strategy.calculate(a, b); } }

注意我同时保留了构造器注入和 setter 注入两种方式,这在某些动态场景下非常有用。比如你的程序在运行过程中,根据用户的交互行为改变计算规则,setter 注入就是唯一的解。

3.4 客户端调用

public class Client { public static void main(String[] args) { CalculatorContext context = new CalculatorContext(new AddStrategy()); System.out.println("3 + 5 = " + context.execute(3, 5)); // 运行时切换策略 context.setStrategy(new MultiplyStrategy()); System.out.println("3 * 5 = " + context.execute(3, 5)); } }

跑一下,输出如下:

3 + 5 = 8 3 * 5 = 15

这个 Demo 虽然简单,但已经完整展示了策略模式的三个核心动作:定义统一接口、编写多个实现、由上下文对外提供服务。

如果你只是想入门,看到这里其实就够了。但现实项目里,我们几乎不会这么“裸用”策略模式,而是会结合 Spring 容器让它发挥更大的威力。下面进入正题。

4. 在 Spring 项目里落地的正确姿势

4.1 为什么不用 new

上面的 Demo 中,你看到了new AddStrategy()这种写法。在真实项目中,我们基本不会直接在业务代码里new策略对象,原因有两个:

  1. 对象生命周期管理混乱:策略类大多是无状态的(不持有成员变量,或只持有不可变配置),频繁创建和销毁没有意义,交给 Spring 管理成单例更合理;
  2. 调用方仍然依赖具体类:虽然CalculatorContext解耦了,但如果客户端还要new,实际上策略的选择逻辑还是散落各处。

我们的目标是:让调用方根本感觉不到策略类的存在,传入一个类型标识,自动完成分发。

4.2 用 Spring 管理策略族

先定义策略接口,这次我们拿真实的业务场景来说——会员订单折扣计算:

public interface DiscountStrategy { // 返回这个策略对应的用户类型或场景标识 String getUserType(); // 根据原价计算折扣后价格 BigDecimal calculateDiscount(BigDecimal originalPrice); }

接口里加了一个getUserType()方法,为什么要加?因为下面要用它和 Spring 的 Bean 名称/集合特性配合,实现自动路由。

实现三个策略类,并注册为 Spring Bean:

@Component public class NewUserDiscountStrategy implements DiscountStrategy { @Override public String getUserType() { return "new_user"; } @Override public BigDecimal calculateDiscount(BigDecimal originalPrice) { // 新用户,统一打 8.5 折 return originalPrice.multiply(new BigDecimal("0.85")); } }
@Component public class VipDiscountStrategy implements DiscountStrategy { @Override public String getUserType() { return "vip"; } @Override public BigDecimal calculateDiscount(BigDecimal originalPrice) { // VIP 用户,满 199 减 80,不满则原价 if (originalPrice.compareTo(new BigDecimal("199")) >= 0) { return originalPrice.subtract(new BigDecimal("80")); } return originalPrice; } }
@Component public class DefaultDiscountStrategy implements DiscountStrategy { @Override public String getUserType() { return "default"; } @Override public BigDecimal calculateDiscount(BigDecimal originalPrice) { // 默认用户,不打折 return originalPrice; } }

一句话总结这三个类:getUserType()是策略的“门牌号”,calculateDiscount()是策略的“实际动作”。

4.3 核心:利用 Spring 自动注入策略 Map

这是整个实战中最关键、也最让人眼前一亮的地方。Spring 支持将某个接口的所有实现类自动收集到一个Map中,Map 的 key 就是 Bean 名称。

但我们不会直接用 Bean 名称作为路由,因为 Bean 默认名称是类名首字母小写,比如newUserDiscountStrategy,不够直观、也不够稳定。所以我们需要做一个“别名映射”。

先看代码:

@Service public class DiscountStrategyContext { private final Map<String, DiscountStrategy> strategyMap; // Spring 自动将策略接口的所有实现类注入到这个 Map 中 public DiscountStrategyContext(Map<String, DiscountStrategy> strategyMap) { this.strategyMap = strategyMap; } /** * 根据用户类型获取对应策略 */ public DiscountStrategy getStrategy(String userType) { // 重新构建映射关系:策略标识 -> 策略对象 return strategyMap.values().stream() .filter(strategy -> strategy.getUserType().equals(userType)) .findFirst() .orElse(strategyMap.get("defaultDiscountStrategy")); } public BigDecimal execute(String userType, BigDecimal originalPrice) { DiscountStrategy strategy = getStrategy(userType); return strategy.calculateDiscount(originalPrice); } }

这段代码干了什么?我拆解一下:

  • Map<String, DiscountStrategy> strategyMap:Spring 看到构造器参数是一个 Map,并且泛型 key 是 String,会自动把容器中所有DiscountStrategy类型 Bean 收集进来,key 是 Bean 名称;
  • getStrategy:遍历这个 Map,根据每个策略自己声明的getUserType()找到匹配项;
  • orElse兜底:找不到对应策略时,回退到默认策略defaultDiscountStrategy,避免空指针。

这里有小伙伴会问:为什么不直接用 Bean 的名称作为 key?因为 Bean 名称是约定俗成的类名首字母小写,换个人重构类名就全乱了。而getUserType()是策略自己声明出来的标识,业务语义更强,也更稳定。

4.4 controller 层调用

@RestController @RequestMapping("/order") public class OrderController { private final DiscountStrategyContext strategyContext; public OrderController(DiscountStrategyContext strategyContext) { this.strategyContext = strategyContext; } @PostMapping("/price") public BigDecimal calculate(@RequestBody OrderRequest request) { return strategyContext.execute(request.getUserType(), request.getOriginalPrice()); } }

调用方传入什么用户类型,内部就自动路由到对应策略。业务层不需要出现任何if。注意,这个版本用的是 Java 8 的 Stream 遍历,性能完全够用,因为策略数量通常是个位数到十几个,不是性能瓶颈。

如果你用的是更现代的写法(比如 Java 8 的Map.merge或者直接构造一个HashMap手动填充),也是可以的。但上面这种基于 Spring 自动注入的风格最干净,也最符合主流项目的习惯。

4.5 直接在 Map 上做逻辑的优化空间

有经验的开发可能已经发现,每次调用getStrategy时都要遍历整个 Map,不够优雅。虽然量小无所谓,但我们可以做一个“更聪明”的初始化:

@Service public class DiscountStrategyContext { private final Map<String, DiscountStrategy> strategyMap = new HashMap<>(); // Spring 会把所有实现注入到 list 中 public DiscountStrategyContext(List<DiscountStrategy> strategies) { // 在构造阶段就建立好路由表 for (DiscountStrategy strategy : strategies) { this.strategyMap.put(strategy.getUserType(), strategy); } } public DiscountStrategy getStrategy(String userType) { return strategyMap.getOrDefault(userType, strategyMap.get("default")); } public BigDecimal execute(String userType, BigDecimal originalPrice) { DiscountStrategy strategy = getStrategy(userType); return strategy.calculateDiscount(originalPrice); } }

把“路由表的构建”放在构造函数里完成,之后每次查询都是 O(1) 的 HashMap 查找。这种写法还解决了一个隐患:如果有两个策略返回了相同的getUserType(),在构建阶段就会覆盖掉前面的值,你很快就能发现问题(或者也可以用检测重复的代码提前报错),而不是等到调用时才出现诡异行为。

我给个小建议:如果策略数量比较少,用 Stream 遍历即可,代码更直观;如果策略种类比较多,或者对性能有轻微要求,用第二种“构建路由表”的方式,差别其实也不大。但无论如何,别在设计上把策略标识写死成“约定”,一定要让每个策略自己报告身份,这样以后加策略才真正省事。

5. 策略模式 + 枚举与函数式接口的“现代玩法”

如果你觉得上面的 Spring 装配方案还不够过瘾,我再介绍一种近两年在项目里越来越常见的写法:策略模式结合枚举和Function接口。

这种写法特别适合那种“策略体量小、一个方法就搞定”的场景,能把代码量压缩到极致,同时不失可读性。

public enum DiscountStrategyEnum { NEW_USER("new_user", price -> price.multiply(new BigDecimal("0.85"))), VIP("vip", price -> price.compareTo(new BigDecimal("199")) >= 0 ? price.subtract(new BigDecimal("80")) : price), DEFAULT("default", price -> price); private final String userType; private final Function<BigDecimal, BigDecimal> handler; DiscountStrategyEnum(String userType, Function<BigDecimal, BigDecimal> handler) { this.userType = userType; this.handler = handler; } public static BigDecimal execute(String userType, BigDecimal originalPrice) { for (DiscountStrategyEnum strategy : values()) { if (strategy.userType.equals(userType)) { return strategy.handler.apply(originalPrice); } } return DEFAULT.handler.apply(originalPrice); } }

调用:

BigDecimal price = DiscountStrategyEnum.execute("vip", new BigDecimal("230"));

这种写法本质上是“策略模式 + 枚举 + lambda”的组合拳,三个角色的代码全部收拢到一个枚举里。它的最大优势是代码集中、一眼就能看出所有策略全貌;最大劣势是策略之间如果有大量共享逻辑,就不容易复用。

所以我的建议是:

  • 策略方法体只有一两行的,优先用枚举 + lambda 就够了;
  • 策略方法体超过十行、或需要依赖多个服务组件的,用 Spring Bean + 上下文的完整策略模式。

两者不冲突,甚至可以混合使用。好的设计从来不是“只用一种模式”,而是“在最合适的位置用最合适的方式”。

6. 常见问题与排查技巧实录

这块内容是我的“独家干货担当”。下面这些问题全部来自真实开发过程中的现身说法,有的问题不是看两篇博客就能知道的。

6.1 Spring 注入 Map 后 Bean 名称对不上的问题

新手容易踩的第一个坑:Map 注入后,key 是 Bean 名称没错,但当你用自定义的getUserType()去查找时,发现查不到。原因一般有两个:

  • 类上没有加@Component/@Service,Spring 容器里根本没有这个 Bean,Map 里自然是空的;
  • getUserType()返回的字符串和传入的userType不一致,比如多了一个空格、大小写不同。

排查方法:可以在DiscountStrategyContext的构造函数中加一个断点,或者干脆在启动的时候打日志,把 Map 的 key 和每个策略上报的getUserType()打出来一览无遗。让“锅”浮出水面,别靠肉眼去读代码。

6.2 多个策略类导致的无用类数量膨胀

这是“过度设计”的典型反面例子。有些团队为了用设计模式而用设计模式,散弹枪式地创建七八个策略类,每个类就写了一行 return,抽象过度,反而让新人看不懂。

解决办法:评估策略的复用性和复杂度。如果一个策略只有一行 return,且没有多个地方复用,那这个策略就没有独立成类的必要,用枚举或直接在调用处做条件判断更合适。策略模式的意义在于“算法族的可替换性”,不是“消灭所有的 if”。有些 if 是业务状态的合法映射,不该被消灭。

我个人的判断标准比较主观:一个策略类的方法体低于三行,且策略总数不超过三个,我就会考虑是否真的要用完整策略模式。重模式的收益主要在长期维护和扩展上,但短期需要付出一定的阅读成本。

6.3 策略里塞了太多代码,变成“上帝策略”

还有另一种反面案例:策略类数量虽然不多,但某个策略类的方法体写了两百行,把所有特殊情况都堆在里头。这其实就不是策略的问题了,而是“策略内部分支过多”。

处理思路是:如果一个策略太复杂,应继续拆分成子策略,或者用模板方法模式固定主流程,把可变部分继续向下抽象。策略模式可以嵌套使用,这不是什么丢人的事。嵌套时注意保持每个子策略的命名清晰,别最后自己都找不到北。

6.4 运行时动态切换策略为什么不生效

有些同学在 controller 里先getStrategy拿到了策略对象,之后想换策略,发现换不掉。原因是策略上下文持有的是策略对象的副本,而你改的是另一个引用。

这种问题通常出在:

  • 没有通过上下文更新策略,而是直接改了局部变量;
  • 或者策略被 Spring 配置成了单例,但你在策略内部不小心写了可变的成员变量。

我的建议:策略类尽量设计成无状态的(不持有可变成员变量)。如果确实需要携带配置参数,可以用@Value注入只读配置,但不要在策略类里声明可变的HashMap之类的东西。多线程环境下,这会造成非常隐蔽的 bug。

6.5 策略内部需要用 Spring 管理的其他 Bean

策略类也是 Bean,所以可以直接通过构造器把需要的其他组件注入进来:

@Component public class VipDiscountStrategy implements DiscountStrategy { private final CouponService couponService; public VipDiscountStrategy(CouponService couponService) { this.couponService = couponService; } @Override public BigDecimal calculateDiscount(BigDecimal originalPrice) { BigDecimal vipDiscount = originalPrice.subtract(new BigDecimal("80")); // 还可以叠加优惠券 return couponService.applyCoupon(vipDiscount, "VIP_COUPON"); } }

这正好演示了策略模式的“可组合性”:具体策略内部完全可以调用其它服务,它的职责只是“算折扣”,至于折扣计算过程中需要查表、校验、准备数据,是它自己的事。

7. 策略模式背后的设计思想

很多初学者困惑:策略模式就这么简单?为什么那么多资深前辈强调它的价值?

我理解,策略模式真正想解决的,不是“消除 if-else”这个问题本身,而是让代码的可变部分和不可变部分相互隔离:

  • 不可变部分:调用的入口(getStrategy)、路由方式(Map 映射)、默认兜底逻辑;
  • 可变部分:每个策略内部的业务规则。

这种隔离带来了三个长期收益:

  1. 可测试性高:每个策略可以独立写单元测试,不需要启动整个 Spring Boot 应用,也不需要 mock 一堆不相关的类;
  2. 变更成本低:新增一个策略,只需新增一个类,不改动任何现有类,代码评审时 diff 极小,回归测试的范围也极小;
  3. 新人友好:新同学接手时,只需要看DiscountStrategyContext和策略接口,就能理解整个规则体系的运作方式,不需要在一个几百行的 if-else 里考古。

还有人会问:这和“面向接口编程”有什么区别?区别就是策略模式把“用接口来调用算法”和“算法的运行时选择”两者统一了起来。面向接口编程是基本功,而策略模式是基本功在具体业务场景中的一种经典编排方式。

8. 工具选型与代码评审时的关注点

这一节回答一个很多人关心的问题:策略模式在代码评审中出现时,评审者一般会看什么?也能帮你自查。

关注点一:策略标识是否稳定。getUserType()这类标识字段,不应该取自用户可修改的原始输入,而应该来自后端内部定义好的枚举或字典。否则用户传什么值,策略就随便命中,安全性带隐患。

关注点二:兜底策略是否存在。永远留一个 default 策略,因为路由查不到的情况一定会出现,与其让代码报空指针,不如让它走一个明确的默认行为。

关注点三:策略之间是否存在隐式耦合。有些策略可能依赖“前一个策略已经执行过”的上下文状态,这种隐式耦合一旦出现,策略就退化成了一种顺序执行的 paint 流程,不再是独立的算法族了。这时候需要重新梳理一下拆分粒度和调用时序。

关注点四:Spring 的注入写法是否干净。见过有人在策略 Map 的获取上又包了一层@Autowired字段注入,混合了字段注入和构造器注入风格,代码虽能运行,但一致性很差,后续维护会增加心理负担。建议全项目统一用构造器注入。

9. 策略模式在真实项目中的一个完整场景还原

我常喜欢把知识放在一个完整的场景里去验证。这里我构造一个尽量贴近实际业务需求的小项目,把它从头到脚走一遍,最终代码的结构你可以直接“抄”。

场景:某商城支持“多支付方式结算”。不同支付方式的手续费率、封顶金额、打折系数不同。支付方式包括:余额支付、微信支付、支付宝支付、银行卡支付。每种方式的计费规则如下:

支付方式计费规则
余额支付手续费率为 0
微信支付手续费率为 0.6%,最高 20 元
支付宝支付手续费率为 1%,最高 25 元
银行卡支付手续费率为 1.2%,最高 50 元

这个场景非常适合策略模式,因为:

  • 支付方式会不断增加(比如后续扩展“数字人民币支付”);
  • 每种支付方式的费率算法互相独立、互不干扰;
  • 调用的入口是统一的服务,外部只需要传入支付方式标识。

接口:

public interface PaymentFeeStrategy { String getPaymentType(); BigDecimal calculateFee(BigDecimal amount); }

实现类之一:

@Component public class WechatPayFeeStrategy implements PaymentFeeStrategy { @Value("${pay.wechat.rate:0.006}") private BigDecimal rate; @Value("${pay.wechat.cap:20}") private BigDecimal cap; @Override public String getPaymentType() { return "wechat"; } @Override public BigDecimal calculateFee(BigDecimal amount) { BigDecimal fee = amount.multiply(rate); return fee.min(cap); } }

其它支付方式类似。然后上下文:

@Service public class PaymentFeeContext { private final Map<String, PaymentFeeStrategy> strategyMap; public PaymentFeeContext(List<PaymentFeeStrategy> strategies) { strategyMap = new HashMap<>(); for (PaymentFeeStrategy strategy : strategies) { strategyMap.put(strategy.getPaymentType(), strategy); } } public BigDecimal calculateFee(String paymentType, BigDecimal amount) { PaymentFeeStrategy strategy = strategyMap.get(paymentType); if (strategy == null) { throw new IllegalArgumentException("Unsupported payment type: " + paymentType); } return strategy.calculateFee(amount); } }

这里我故意没有加兜底策略,而是直接抛出异常。为什么?因为支付方式不支持时是硬错误,不应该是默认行为,这和用户折扣场景的“默认策略”语义不同。这个细节说明一个道理:模式是骨架,业务语义才是血肉,不能机械套用。

测试类可以这样写:

@SpringBootTest class PaymentFeeContextTest { @Autowired private PaymentFeeContext context; @Test void wechatFee() { BigDecimal fee = context.calculateFee("wechat", new BigDecimal("100")); // 100 * 0.006 = 0.6 assertEquals(new BigDecimal("0.6"), fee); } @Test void wechatFeeCap() { BigDecimal fee = context.calculateFee("wechat", new BigDecimal("5000")); // 5000 * 0.006 = 30 -> 封顶 20 assertEquals(new BigDecimal("20"), fee); } }

这套结构放回真实项目里,无论是扩展新的支付方式,还是调整某一种支付方式的费率,都只需要动很小的一块环境,完全符合开闭原则。

10. 策略模式常见误用自查清单

现象问题根源该怎么做
策略类数量爆炸,每个策略就一行代码过度抽象考虑用枚举 + 函数式接口替代
上下文里出现大量 if-else路由逻辑没抽象好用 Map 构建路由表替代
策略类里持有可变状态线程安全风险改成无状态或使用局部变量
调用方频繁新建策略对象没有利用 Spring 容器交给 Spring 管理成单例
策略标识写死在字符串里维护困难易脏数据定义枚举常量统一管理
不同策略间有顺序依赖拆分粒度不当将顺序逻辑放进编排层,而不是策略内部

这张表建议收藏,每次自测代码时对照一遍,可以帮你避开大部分设计层面的坑。

11. 我在团队里推行策略模式时的三个心得

代码说完了,最后聊点软性的东西。

第一,警惕“模式化改造强迫症”。我曾经在一个项目里,看到某个同学把一段三行的 if-else 硬生生套上策略模式,额外产生了四个类。这种改造除了让类图变得花哨之外,没有带来任何实际收益。先问自己三个问题:这段逻辑是否频繁变更?算法是否互相独立?调用方是否需要动态切换行为?三个问题答案只要不是“是”,就别动刀。

第二,策略模式的蔓延速度比想象中快。一旦你落地了一处,业务方尝到了“加功能不回归”的甜头,很快就会在更多场景复制这个模式。这本来是好事,但要注意控制策略粒度的统一。我会和一个团队的成员约定一个简单规范:策略接口的语义必须单一,比如“计算折扣”和“计算运费”不要混在同一个接口里。

第三,文档要跟着策略实现走。策略类一多,如果没有人维护一张“策略 - 标识 - 生效规则”的速查表,后续维护者很快就晕了。我把这张表放在代码仓库的 markdown 文件里,每次新增策略时要求同时更新速查表。这不是强制规范,但做了几次之后,大家都发现可以少问很多“这个策略是干嘛的”的重复问题。

最后再分享一个小技巧:在策略类比较多的时候,可以在类名上用业务前缀统一风格,比如OrderDiscountStrategy、OrderShippingStrategy,这样通过 IDE 的类名搜索,就能快速定位到指定领域中所有可用的策略,比直接改Map里的 key 要直观得多。

希望这篇从概念到实战、从代码到心法的长文,能让你彻底拿捏策略模式。下次再看到那种几十个分支的 if-else 时,你就有底气把它优雅地重构掉了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 3:09:08

AI辅助科研绘图全攻略:从原始数据到期刊级图表

做了这么多年科研图表&#xff0c;最怕的不是数据算不出来&#xff0c;而是数据都躺好了&#xff0c;图却拿不出手。默认配色像上个世纪的遗留物&#xff0c;坐标轴刻度密密麻麻看不清&#xff0c;柱子还带描边阴影&#xff0c;放到投出去的论文里&#xff0c;连自己都觉得寒碜…

作者头像 李华
网站建设 2026/10/22 3:09:04

LatentVLA:用潜在动作预测代替文本坐标,登顶NAVSIM SOTA

自动驾驶圈这几天都在传这样一个结果&#xff1a;NAVSIM 榜单又刷新了&#xff0c;拿下 SOTA 的是一套叫 LatentVLA 的自驾 VLA 方案&#xff0c;核心思路非常直接——用“潜在动作预测”代替“文本坐标输出”。先给没跟进的读者一句话版本&#xff1a;以往 VLA 模型要像写作文…

作者头像 李华
网站建设 2026/10/22 3:08:56

大功率液力变速器振动信号智能诊断:从特征提取到随机森林实战

干设备状态监测这几年&#xff0c;我最大的感受是&#xff1a;真正的故障不会提前给你打报告&#xff0c;但一定会在振动信号里留下痕迹&#xff0c;关键是你有没有办法把痕迹找出来。前段时间接手了一套大功率液力变速器智能检测与诊断系统的开发任务&#xff0c;目标是给重载…

作者头像 李华
网站建设 2026/10/22 3:06:30

SpringBoot+Vue大创项目管理系统设计与实现要点解析

1. 项目概述与整体设计思路1.1 大创管理系统到底做什么大创是“大学生创新创业训练计划”的简称&#xff0c;很多高校每年都会组织学生申报国家级、省级、校级的大创项目。这个系统的核心痛点在于&#xff1a;传统模式下&#xff0c;项目申报、中期检查、结题验收全部依赖纸质材…

作者头像 李华
网站建设 2026/10/22 3:06:16

电影数据关联规则挖掘实战:Apriori算法与导演选演员倾向分析

简介&#xff1a;面向数据分析与机器学习初学者&#xff0c;这份资源以电影数据为样例&#xff0c;演示如何用关联规则挖掘分析导演选演员的偏好倾向&#xff0c;也可推广至商品捆绑销售等常见业务场景。资源共9个文件&#xff0c;压缩包仅1.01MB&#xff0c;包含Python脚本、M…

作者头像 李华
网站建设 2026/10/22 3:06:10

MiroThinker-v1.5:从检索规划到自评反思,打造最强搜索智能体

前几天在社区聊Agent应用时&#xff0c;有位朋友问我&#xff1a;搜索场景为什么非要单独做一个智能体&#xff0c;直接让大模型联网不就行了&#xff1f;我拿这次放出的MiroThinker-v1.5实测数据给他算了笔账&#xff1a;同样一堆来源片段&#xff0c;普通联网问答能把一半信息…

作者头像 李华