资讯动态

Java策略模式实战:告别if-else,打造可扩展业务逻辑

发布时间:2026/10/9 16:10:31 来源:尧图企业网站定制
在 Java 项目里写久了你会发现最让人头疼的不是新需求本身而是“又要改老代码”。我这里有个真实例子早期的支付模块只有支付宝一个渠道代码很清爽后来加了微信支付又加了跨境信用卡又加了本地钱包……几个月过去那个原本 30 行的 pay() 方法膨胀到了 200 多行。产品经理提一个小改动你都得小心翼翼地在十几个 if-else 分支里找位置。后来我把这块重构成策略模式才真正理解了“可扩展业务逻辑设计”不是一句空话。这篇文章不打算讲什么高深理论而是从我的实际项目经验出发把 Java 项目中策略模式的完整用法捋一遍。你会看到纯 Java 的原生写法、Spring Boot 工程化落地、一次真实重构案例还有我踩过的坑和面试中被追问最多的问题。适合正在重构 if-else 地狱的 Java 开发也适合准备面试、想把设计模式讲出项目味道的朋友。1. 为什么你的业务代码会越来越难维护1.1 一段不断膨胀的 if-else 究竟带来了什么先还原一个很常见的场景。业务早期只有一种计价方式方法很简单public double calculatePrice(double originalPrice, int memberLevel) { if (memberLevel 0) { return originalPrice; } return originalPrice * 0.9; }看起来没问题。但需求方不会就此停下。普通会员、黄金会员、铂金会员、黑卡会员再来个“生日月双倍折扣”“企业客户专享价”……于是代码变成这样public double calculatePrice(double originalPrice, int memberLevel, boolean isBirthdayMonth, boolean isEnterprise) { if (memberLevel 0) { if (isBirthdayMonth) { return originalPrice * 0.95; } return originalPrice; } if (memberLevel 1) { if (isEnterprise) { return originalPrice * 0.85; } return originalPrice * 0.9; } if (memberLevel 2) { // ... 继续叠 } // ... 此处省略五十行 }这种代码的可怕之处不在于字符多而在于每个分支都在同一个方法里挤着。你改一个分支就动了一次全方法测试要回归全量同事代码合并必然冲突连线上日志都只显示一个巨大的方法名你分不清用户到底走了哪条路。核心问题是什么是“计价规则”和“调用方”耦合在了一个方法里违背了单一职责和开闭原则——加一个需求就得改一段已经验证过的老逻辑。1.2 策略模式解决什么问题定义、角色与形象类比策略模式的定义很简单定义一族算法把每个算法分别封装起来让它们之间可以互相替换。它在面向对象中的本质是利用“多态”把“变化的部分”抽离出来让调用方面向抽象编程而不是面向具体实现编程。我一向喜欢用一个类比来解释它手机导航。你要从 A 地去 B 地目的地是固定的但路线偏好有“距离最短”“时间最短”“高速优先”“少收费”好几种。导航软件不需要为你每种偏好写一套完整的导航系统它只需要把算路引擎固定下来再注入不同的“路线策略”就行。你在界面上切换底层只是换了一个策略对象。对应到设计模式里四个角色分别是策略接口定义统一的行为方法比如 calculate。具体策略类各自实现一套算法互不干扰。上下文对象持有一个策略引用负责调用策略方法它不需要知道策略内部的细节。客户端决定创建哪个策略并交给上下文。这种结构带来的直接好处是新增一种会员等级我不需要打开原有方法只需要新增一个策略类。老逻辑一动不动回归范围被锁死在这一个新类上。1.3 什么情况下不要用策略模式写了多年代码我也必须坦诚一句策略模式不是银弹它不是用来替所有 if-else 挡刀的。至少满足下面三条之一我才建议引入同一行为存在多个实现并且运行时会根据条件切换。if-else 分支数量还在随着版本不断增加而且每个分支里都是一段独立算法。你明确知道后续会有新实现加入并且希望新增实现时不碰老代码。如果业务只有一个实现或者需求方明确说“以后不会变”那硬套策略模式反而会把简单的逻辑拆得七零八落增加调用链长度和阅读成本。还有一点你最好提前知道策略模式并不会减少总代码量它只是把“膨胀在一个方法里”的代码拆分成了“多个各司其职的小类”。这是用类的数量换清晰度和可维护性值不值取决于业务是否真的有扩展需求。2. Java 原生实现从接口到注册器的完整落地2.1 场景设计会员折扣计算这一节先不看 Spring只讲纯 Java 怎么把这个模式落地。我用一个非常常见的“会员折扣”做例子普通会员不打折黄金会员 9 折铂金会员 85 折后面随时可能加“黑卡会员 8 折”。原价 300 元的商品黄金会员应该付 270.00 元铂金会员应该付 255.00 元。这个场景虽然简单但足以展示策略模式的核心结构。你很快会发现后面所有复杂业务——支付渠道、运费模板、营销活动——都是在这个骨架上长出来的。2.2 策略接口、实现类与上下文类的完整代码先定义一个策略接口import java.math.BigDecimal; import java.math.RoundingMode; public interface DiscountStrategy { /** * 根据原价计算折后价 */ BigDecimal calculate(BigDecimal originalPrice); }然后是三个具体策略类。用 BigDecimal 而不是 double是因为金额计算不允许有浮点误差这是做支付、电商类项目的基本素养public class NormalMemberDiscount implements DiscountStrategy { Override public BigDecimal calculate(BigDecimal originalPrice) { return originalPrice; } } public class GoldMemberDiscount implements DiscountStrategy { private static final BigDecimal DISCOUNT_RATE new BigDecimal(0.90); Override public BigDecimal calculate(BigDecimal originalPrice) { return originalPrice.multiply(DISCOUNT_RATE).setScale(2, RoundingMode.HALF_UP); } } public class PlatinumMemberDiscount implements DiscountStrategy { private static final BigDecimal DISCOUNT_RATE new BigDecimal(0.85); Override public BigDecimal calculate(BigDecimal originalPrice) { return originalPrice.multiply(DISCOUNT_RATE).setScale(2, RoundingMode.HALF_UP); } }注意黄金会员 9 折不是 0.9 乘完就完事。打完折之后要 setScale(2, RoundingMode.HALF_UP)保留两位小数并四舍五入。如果你在真实交易系统里把这一步省了后面出账对不上时哭都来不及。上下文类来了。这里的 OrderPricingContext 就是“持有策略”的角色它自己不实现任何折扣算法只是把计算行为转发给当前策略public class OrderPricingContext { private DiscountStrategy strategy; public OrderPricingContext(DiscountStrategy strategy) { this.strategy strategy; } public void setStrategy(DiscountStrategy strategy) { this.strategy strategy; } public BigDecimal calculate(BigDecimal originalPrice) { return strategy.calculate(originalPrice); } }调用起来是这样的OrderPricingContext context new OrderPricingContext(new GoldMemberDiscount()); BigDecimal payable context.calculate(new BigDecimal(300.00)); System.out.println(payable); // 270.00这个结构最大的优势是什么测试的时候你可以直接 new 一个 GoldMemberDiscount单独验证它的算法不需要准备完整的订单、用户、商品对象。一个策略一个测试类逻辑清清爽爽。2.3 用注册器消除客户端 if-else但读到这里你可能会说这不还是得在调用处自己写 if 判断创建哪个策略吗没错所以真正的工程实践里通常还要加一个“策略注册器”把策略的创建和选择集中管理起来。import java.util.HashMap; import java.util.Map; public class DiscountStrategyRegistry { private final MapString, DiscountStrategy registry new HashMap(); public DiscountStrategyRegistry() { registry.put(normal, new NormalMemberDiscount()); registry.put(gold, new GoldMemberDiscount()); registry.put(platinum, new PlatinumMemberDiscount()); } public DiscountStrategy get(String memberLevel) { DiscountStrategy strategy registry.get(memberLevel); if (strategy null) { // 默认回到普通会员避免空指针以及线上活动配错 key 时直接报错 return registry.get(normal); } return strategy; } }这个注册器就是一个轻量工厂。业务代码里不再出现任何 if-else只需要一行DiscountStrategy strategy registry.get(memberLevel); BigDecimal payable strategy.calculate(orderOriginalPrice);把“选择策略”的逻辑收敛到一个类里是策略模式工程化落地中最重要的一步。否则你虽然在每个策略内部实现了多态但调用处还是散落一堆判断等于白做。3. Spring Boot 工程化让策略自动装配进业务层3.1 利用容器注入 Map 收集所有策略 Bean纯 Java 手工注册有个问题每新增一个策略类你都要记得去注册器里 put 一下忘一次就是一个线上 bug。在 Spring Boot 项目里这个问题可以彻底解决——直接让容器帮你把所有策略 Bean 收集到一个 Map 里。先给每个策略类标上 Bean 注解并显式指定一个业务语义清晰的 beanNameService(normal) public class NormalMemberDiscount implements DiscountStrategy { // ... } Service(gold) public class GoldMemberDiscount implements DiscountStrategy { // ... } Service(platinum) public class PlatinumMemberDiscount implements DiscountStrategy { // ... }然后在你需要用到策略的业务 Service 里直接注入这个 Mapimport lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.Map; Service RequiredArgsConstructor public class SettlementService { private final MapString, DiscountStrategy discountStrategyMap; public BigDecimal calculatePayable(String memberLevel, BigDecimal originalPrice) { DiscountStrategy strategy discountStrategyMap.get(memberLevel); if (strategy null) { strategy discountStrategyMap.get(normal); } return strategy.calculate(originalPrice); } }Spring 在启动时会扫描所有 DiscountStrategy 类型的 Bean自动放入这个 Mapkey 就是 beanName。以后你新增一个黑卡策略只需要写一个类、加一个 Service(black) 注解业务代码一行都不用改。这就是“可扩展业务逻辑设计”在实际项目里的体感加需求时不再打开老代码而是在旁边添新类。3.2 跨境多商户商城的支付渠道策略示例很多读者可能正在做或者想参考“Spring Boot MyBatis 的多商户跨境商城”这类开源项目支付渠道正是策略模式最典型的战场。同一个订单不同用户可能选择不同的支付方式不同商户也可能配置不同的收款渠道。如果用 if-else 写支付路由渠道一旦超过五个那个方法基本就是灾难。先定义一个支付策略接口public interface PaymentStrategy { /** * 发起支付 * return 支付是否已成功提交不一定等于最终成功 */ boolean pay(PaymentRequest request); }每个渠道一个实现类。比如支付宝渠道负责组装支付宝 SDK 参数、验签、调起支付微信渠道走微信支付协议跨境卡渠道另外处理货币转换和 3DS 验证Service(alipay) public class AlipayPaymentStrategy implements PaymentStrategy { Override public boolean pay(PaymentRequest request) { // 调用支付宝 SDK 的核心逻辑 return true; } } Service(wechat) public class WechatPaymentStrategy implements PaymentStrategy { Override public boolean pay(PaymentRequest request) { // 组装微信支付参数 return true; } } Service(crossBorderCard) public class CrossBorderCardPaymentStrategy implements PaymentStrategy { Override public boolean pay(PaymentRequest request) { // 跨境卡支付货币换算 3DS 校验 return true; } }业务侧只认一个 channelCodeService RequiredArgsConstructor public class PaymentService { private final MapString, PaymentStrategy paymentStrategyMap; public boolean executePayment(String channelCode, PaymentRequest request) { PaymentStrategy strategy paymentStrategyMap.get(channelCode); if (strategy null) { throw new IllegalArgumentException(不支持的支付渠道: channelCode); } return strategy.pay(request); } }后面要接新渠道新增一个实现类加一个 Bean 注解完事。旧渠道哪怕正在线上稳定运行也碰不到一行代码。我在不少多商户项目里都是这么设计的渠道从 3 个扩展到 20 个时支付路由代码始终保持这个形状。3.3 与 MyBatis-Plus 等持久层组件如何分工有一个非常常见的问题策略模式和 MyBatis-Plus 怎么配合我见过有人用 MyBatis-Plus 根据 Java 实体类生成建表 SQL也见过有人想用“策略”去动态选择操作哪张表。这里想把责任边界理清楚MyBatis-Plus 解决的是持久层“实体与表之间的映射”“增删改查的编排”而策略模式解决的是 Service 层“业务规则的多态选择”两者是不同维度的东西。在一个标准的分层架构里你完全可以让 MyBatis-Plus 把商品、订单、会员的 CRUD 做得很薄然后在 Service 层用策略模式承载计费规则、渠道适配、营销活动。你可以用 MyBatis-Plus 查出用户的会员等级再交给策略 Map 去选择对应的折扣实现但你不应该为了让代码“看起来更设计模式”而在 DAO 层硬套策略。持久层如果出现“按渠道选不同表、不同数据源”那一般是多数据源路由或分表组件的职责和业务策略不能混为一谈。分层清晰的项目里策略模式从来不是和持久层二选一的关系而是各管一摊配合着让业务代码保持可扩展。“可扩展业务逻辑设计”这句话的重点在业务逻辑老老实实待在你的 Service 层和领域层就好。4. 一次真实重构全过程运费计算从 95 行 if-else 到策略集4.1 重构前的代码长什么样我之前处理过一个跨境电商项目的运费模块。不同物流渠道DHL、EMS、云途各自有计费规则还要叠加目的国系数、重量段区间。最开始的实现非常直白public double calculateShipping(ShippingContext ctx) { String channel ctx.getChannelCode(); double weight ctx.getWeightKg(); if (DHL.equals(channel)) { if (weight 0.5) { return 16.0; } double extra Math.ceil(weight - 0.5); if (US.equals(ctx.getDestinationCountry())) { return 16.0 extra * 5.5; } else if (DE.equals(ctx.getDestinationCountry())) { return 16.0 extra * 6.0; } else { return 16.0 extra * 7.0; } } else if (EMS.equals(channel)) { // 又一坨 } else if (YUN.equals(channel)) { // 再一坨 } return 0; }这段代码的致命问题很明显新增一个渠道要打开整个方法改修改 DHL 的续重价格会影响 EMS 的阅读想单独验证“US 地区 DHL 运费”只能构造整个 ShippingContext然后在调试器里一步步走进分支更别说多人同时改动这个方法时Git 合并冲突会让人崩溃。4.2 重构步骤与关键实现重构我分了几步走每一步都先保证行为不变再做结构调整。第一步定义运费策略接口和一个统一的上下文对象public interface FreightCalculator { double calculate(ShippingContext ctx); }ShippingContext 包含渠道编码、重量、目的国、申报价值等信息是各个策略之间的“输入协议”。之所以封装成一个对象而不是散落参数是因为渠道策略在演进过程中几乎必然要增加字段例如旺季附加费、燃油附加费。如果每次加字段都改接口签名所有策略都要跟着动封装成上下文既能承载当前数据后续也能平滑扩展。第二步把每个渠道的规则拆成独立策略类。以 DHL 为例Service(DHL) public class DhlFreightStrategy implements FreightCalculator { private static final double FIRST_WEIGHT_KG 0.5; private static final double FIRST_PRICE 16.0; private static final double CONTINUE_PRICE 5.5; Override public double calculate(ShippingContext ctx) { double weight ctx.getWeightKg(); if (weight FIRST_WEIGHT_KG) { return FIRST_PRICE; } // 超过首重后不足 1kg 按 1kg 计费所以用 Math.ceil 向上取整 double extraKg Math.ceil(weight - FIRST_WEIGHT_KG); return FIRST_PRICE extraKg * CONTINUE_PRICE; } }看到 Math.ceil 别奇怪这是物流行业的真实规则2.3kg 的包裹超出首重 1.8kg不是按 1.8kg 算续重而是按 2kg 算。带头一个 2.3kg 走 DHL 到美国的运费就是 16 2 * 5.5 27.0 元。第三步做一个默认兜底策略。渠道没匹配上时宁可给一个默认规则也不能直接返回 0 让订单漏计费Service(default) public class DefaultFreightStrategy implements FreightCalculator { Override public double calculate(ShippingContext ctx) { return 20.0; // 兜底一口价 } }第四步业务层通过 Spring 注入的 Map 路由策略Service RequiredArgsConstructor public class ShippingQuoteService { private final MapString, FreightCalculator freightStrategyMap; public double quote(ShippingContext ctx) { FreightCalculator calculator freightStrategyMap.get(ctx.getChannelCode()); if (calculator null) { calculator freightStrategyMap.get(default); } return calculator.calculate(ctx); } }整个重构过程中我没有改变任何运费计算结果只是把膨胀的 if-else 换成了“一个接口 N 个策略类 一个路由点”。改造一旦完成后续每次新增渠道就是写一个类、注册一个 Bean主线调用代码再也不会因为新渠道而改动。4.3 重构前后收益对比我习惯用一张对比表说明这类重构的真实收益这张表你也可以拿去做团队分享时的结论页对比维度重构前 if-else重构后策略模式新增渠道修改原有大方法风险波及全部渠道新增策略类注册 Bean主线代码不动修改单个渠道规则在大方法里找分支容易改错别的渠道只打开对应的策略类范围封死单元测试需要构造大量分支条件测试配置繁琐每个策略类一个测试直接验证算法多人协作同一个方法频繁冲突代码合并痛苦不同渠道在不同类里互不干扰线上排障日志只显示一个大方法日志可直接定位到具体策略实现类代码阅读需要从上到下扫完所有分支按渠道直接进对应类阅读成本骤降重构不是炫技是为了让下一次需求更快落地。这一点在团队里一定要讲清楚否则同事会以为你只是嫌代码丑在没事找事。5. 策略模式常见坑与排查经验5.1 Spring 注入 Map 的 beanName 细节Spring 自动收集 Map 的策略 Bean 用起来很爽但有个细节非常容易踩坑Map 的 key 是 beanName并且大小写敏感。你定义Service(DHL)那 key 就是大写 DHL如果调用处传的渠道编码是小写 dhlmap.get(dhl)得到 null直接走进默认策略而且这种 bug 在测试环境往往发现不了因为资源有限时你测试的渠道编码恰好对得上。我有一次线上事故就是这么来的。运维配置平台里渠道编码写成英文全小写代码里 Bean 名是驼峰结果所有订单运费都走了兜底价 20 元积压了大半天才发现。现在我的做法是渠道编码统一用大写静态常量管理Bean 名和常量保持一致并在策略路由入口加统一的大小写归一化处理至少加一个日志String normalizedCode ctx.getChannelCode().toUpperCase(); FreightCalculator calculator freightStrategyMap.get(normalizedCode); if (calculator null) { log.warn(未知渠道编码: {}命中默认运费策略, ctx.getChannelCode()); calculator freightStrategyMap.get(DEFAULT); }另一个坑是Spring 并不支持把MapChannelEnum, Strategy这种枚举 key 直接注入进来。容器收集策略时只会用 String 作为 key你想用枚举做 key 就得自己再包一层逻辑。更稳的做法是始终保持 String key在业务层用枚举做一层映射校验。5.2 策略类爆炸、无状态与线程安全策略类多了之后有人会开始焦虑本来一个方法 100 行现在变成 8 个类每个类 30 行总量反而更多了。这是策略模式的正常代价不代表代码变差。你要做的不是消灭类数量而是合理组织它们。比如按子域分包支付渠道放strategy.payment运费规则放strategy.freight营销折扣放strategy.promotion。类再多包结构清楚IDE 里一眼就能定位。还有个大坑是线程安全。Spring 容器里的 Bean 默认是单例也就是说所有请求线程共享同一个策略实例。如果有人在策略类里写了一个成员变量来传参比如Service(alipay) public class AlipayPaymentStrategy implements PaymentStrategy { private String currentRequestId; // 错误示范 }高并发下A 用户写入的 requestId 会被 B 用户覆盖线下难复现线上问题定位如坠云雾。真正的原则是策略实现类必须无状态。所有需要变化的数据都通过方法参数或上下文对象传递。我后来在代码评审里看到策略类中出现非 static 的成员变量基本直接打回。5.3 兜底、排序、组合等复杂业务场景真实业务里策略很少像教科书那么干净以下几种情况我实际都处理过。第一策略执行失败需要兜底。比如支付渠道临时不可用不能直接抛异常让用户提交失败而是降级到默认渠道。但要小心实现兜底时最好给降级行为加上埋点和告警不能让系统每次都“静默成功”否则线上故障会被掩盖。第二多个策略同时生效。比如订单结算时“满减、会员折扣、优惠券”都是独立的计费策略并且有固定的执行顺序。我的做法是引入一个组合策略类它内部维护一个有序 ListService public class CompositeDiscountStrategy implements DiscountStrategy { private final ListDiscountStrategy orderedStrategies; public CompositeDiscountStrategy(ListDiscountStrategy orderedStrategies) { // 结合 Order 注解或自定义排序器 this.orderedStrategies orderedStrategies; } }执行时依次调用每个策略再把结果传给下一个等于把多个策略编排成一条流水线。注意叠加折扣时金额精度问题每一步都要用 BigDecimal绝不要用 double。第三策略之间存在优先级。Spring 的 List 注入默认顺序不保证必要时给策略加 Order 注解或者在组合策略里用自定义 Comparator 排序否则你永远无法预测“满减先算还是会员折扣先算”。5.4 面试被问到策略模式怎么答才加分策略模式在 Java 面试里的出镜率极高几乎每个问“设计模式”的面试官都会带着追问。基础问法什么是策略模式这时候别背定义直接说“把可替换的算法封装成独立类调用方面向接口编程通过上下文切换实现互相替换”然后举一个你项目里的真实例子比如支付渠道或运费规则。能让面试官眼睛一亮的是你有没有用过 Spring 注入 Map 的方式自动收集策略 Bean以及你踩过大小写 beanName 的坑没有。追问一般集中在策略模式和状态模式有什么区别核心区别是状态模式通常由状态对象自己驱动状态流转而策略模式由客户端外部选择并替换算法。策略类太多怎么办说清楚先判断策略是否有限且固定如果规则简单可以考虑枚举策略如果策略本身是复杂的业务规则那就按子域分包并考虑配置驱动去指定策略。if-else 和策略模式怎么选不要上来就说“用策略模式替代所有 if-else”要说明白两者不是对立关系策略的入口处依然需要一个选择逻辑只是这个逻辑被收敛到了一处。还有一个小众但加分的方向把行级权限的过滤规则、通知渠道的发送方式这类业务也抽象成策略。当你能从“支付打折”拓展到“权限过滤、消息推送、数据解析”这些场景时面试官会相信你真的在项目里用明白了而不是靠背八股文。6. 策略模式的高阶扩展思路6.1 模板方法模式与策略模式搭配策略模式把“不同的算法”抽了出来但如果这些算法的骨架完全一样只有某一步不同那就该让模板方法模式来配合了。一个典型的例子是通知渠道无论短信、邮件还是站内信发送前都要构建消息体、做鉴权、检查频率限制发送后都要记录发送日志。变化的只有“真正执行发送”这一小步。你可以定义一个抽象模板类public abstract class AbstractNotifySender { public final void send(NotifyContext ctx) { buildMessage(ctx); authenticate(ctx); doSend(ctx); recordLog(ctx); } protected abstract void doSend(NotifyContext ctx); }然后在子类里各自实现 doSend。这里要注意模板方法用继承策略模式用组合两者并不是互斥的。你完全可以在“选渠道”这层用策略模式选 sender再通过模板方法固定发送流程。组合使用之后业务层的扩展点会非常清晰。6.2 责任链与策略组合执行有时候多个策略不是“选一个”而是“按顺序过一遍”这就变成了责任链的思路。比如订单风控校验要依次检查用户黑名单、设备指纹、金额异常、频次限制任何一个环节拒绝就终止。用责任链最舒服每一环决定“继续放行还是拦截”。如果是“每个策略都要执行并累计结果”那就用组合策略。我前面讲到的多折扣叠加就是这种核心是把 List 按顺序注入然后循环执行。这种设计下你要新增一个“直播间专享折扣”不需要碰已有的组合逻辑只需要在策略容器里追加一个带顺序标记的 Bean。6.3 枚举策略与配置驱动当一个业务只有固定几种策略、且每种实现都很短时硬写一堆类确实繁琐。这时候可以直接用枚举实现策略接口public enum MemberLevelDiscount implements DiscountStrategy { NORMAL { Override public BigDecimal calculate(BigDecimal originalPrice) { return originalPrice; } }, GOLD { Override public BigDecimal calculate(BigDecimal originalPrice) { return originalPrice.multiply(new BigDecimal(0.90)) .setScale(2, RoundingMode.HALF_UP); } }, PLATINUM { Override public BigDecimal calculate(BigDecimal originalPrice) { return originalPrice.multiply(new BigDecimal(0.85)) .setScale(2, RoundingMode.HALF_UP); } } }枚举天然是单例直接用类名取值连 Spring 注入那一层都省了。缺点也很明显如果你在枚举里写了几十行复杂逻辑它就会变成一个臃肿的上帝类。所以我只建议在“策略少、规则固定、逻辑短”时用枚举。再进一步很多中后台系统的做法是配置驱动把策略 key 和运行参数存进数据库或配置中心业务侧根据配置选择策略。比如运费规则配置成一行 JSON包含渠道编码、首重、续重单价再有一个策略实现去解析 JSON。这样做的好处是运营改计费规则时不需要重新发版修改配置即刻生效。注意配置驱动是建立在策略模式之上的扩展别为了“不用改代码”而直接把策略判断写到配置里那样会带来新的维护难题。6.4 从注册器到规则引擎的演进当你手里的策略数量越来越多每个策略需要自己的入参校验、优先级、版本、灰度你可能会想引入一个独立的“规则引擎”。其实很多规则引擎的内核就是一个高级版策略注册器——策略表、策略 key、执行顺序、版本号再加上一大套管理和编排界面。在我看来绝大多数项目不需要一上来就上规则引擎。更务实的路线是先用策略模式把代码结构理清等策略数量大到手工配置管理成为瓶颈时再把“策略选择”这个动作配置化给策略类加版本、加开关、加灰度比例。这个时候你发现之前基于策略模式搭出来的结构天然就是规则引擎的一层底座。这也是为什么我总跟团队里的年轻人说设计模式不是 PPT 上的名词而是业务复杂度爬坡时真正能让你少走弯路的地基。我个人在实际项目里的体会是策略模式最适合在第三次出现重复 if-else 时引入太早是过度设计太晚是重构成本飙升。刚上手时也别想着把所有分支一次性拆完先从最常变动、最影响发布的分支下手比如支付渠道、运费规则、营销折扣这一两个区域理顺了整个系统的扩展手感就不一样了。最后分享一个小建议不管用原生注册器还是 Spring 的 Map 注入一定要把“选择策略”的逻辑收敛到同一个地方让新增策略只做“增加”不做“修改”这才是可扩展业务逻辑设计最实在的一条底线。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑