
如果你在Java项目里写过业务逻辑早晚会碰到这样一个场景同一个方法里堆了十几个if-else每加一种规则就要改一次主流程。我第一次认真研究策略模式就是因为订单系统的折扣计算被这些if-else逼到了没法维护的地步。策略模式本身并不复杂核心就一句话把算法和规则封装成独立的策略对象让调用方在运行时选择用哪一个。但真正把它用得顺手需要理解三个角色的边界、知道怎么消灭选择逻辑、还得避开单例和状态这些坑。这篇文章写给Java入门到进阶的开发者用订单折扣、支付渠道这类高频需求当例子从一段能运行但不敢动的代码开始一步步走到Spring容器注入策略的完整实战。1. 先从一段“能跑但不敢动”的代码说起订单折扣里的if-else危机1.1 需求本身不复杂复杂的是它一直在变假设你在维护一个订单系统运营同事每隔一两周就会提一个新的优惠需求。最开始需求很简单普通用户没有折扣VIP用户打九折超级VIP打八折。后来加了满100减20的优惠券又加了新用户首单减5元再后来还有限时秒杀价。如果每个需求都往同一个方法里塞一个分支这个方法很快就会变得非常长而且每个分支之间还可能互相影响。我见过很多项目里的核心计算逻辑就是这么长大的。一开始只有两三个分支看着还挺清晰大家都觉得“没必要搞设计模式”。等到第十个分支加进去再叠加各种优先级、时间窗口、用户等级判断这个方法的长度和嵌套深度已经不是能不能看懂的问题而是没人敢改的问题——任何一行调整都可能引发连锁反应。1.2 三个分支不可怕怕的是分支边界模糊先看一段典型的“初始版本”代码public BigDecimal calculate(BigDecimal amount, String userLevel, boolean hasCoupon) { BigDecimal result amount; if (VIP.equals(userLevel)) { result amount.multiply(new BigDecimal(0.9)); } else if (SUPER_VIP.equals(userLevel)) { result amount.multiply(new BigDecimal(0.8)); } if (hasCoupon amount.compareTo(new BigDecimal(100)) 0) { result result.subtract(new BigDecimal(20)); } if (result.compareTo(BigDecimal.ZERO) 0) { result BigDecimal.ZERO; } return result; }这段代码能跑但已经开始有隐患了。它的问题不在于分支多而在于每次加规则都要改这个方法本身测试范围被迫扩大规则之间的优先级被if-else的顺序和嵌套结构固定死后加的人只能小心翼翼地找位置插入代码可读性差新同事接手的时候不敢动逻辑只敢在末尾追加判断规则之间没有清晰边界VIP折扣和满减券被揉在一起谁也说不清哪个在前、哪个在后。真正让我下决心重构的是产品经理提出了一个组合需求超级VIP用户在下单时如果用了优惠券要先按超级VIP折扣计算再判断是否满足满减条件最后再叠加新用户立减。听到这个需求的时候我就知道continue这个if-else的姿势已经撑不住后续变化了。1.3 策略模式想做的事情把不确定性变成对象策略模式的核心思想一句话就能说完把每一种规则封装成独立的策略对象让主流程只依赖“策略”这个抽象而不依赖具体是哪一种规则。调用方不需要知道内部有多少种实现也不需要知道某个规则具体怎么算只需要告诉上下文“当前是什么场景”上下文会选择合适的策略去执行。这样做的好处是后续加一种新规则、新算法、新渠道不再需要去改动已经稳定的主流程代码只需要新增一个策略实现类然后在注册的地方把对象塞进去。改动从“修改核心逻辑”变成了“新增独立模块”风险边界一下子就清晰了。2. 三个角色先理清策略接口、具体策略、上下文容器策略模式在Java里落地通常由三个角色组成策略接口、具体策略实现类、上下文容器。理解这三个角色的职责边界比记住任何代码模板都重要。2.1 策略接口描述“我要算什么”策略接口定义的是行为和规则不关心具体的实现细节。在订单折扣这个场景里接口可以长这样public interface DiscountStrategy { BigDecimal apply(BigDecimal originalAmount); boolean support(String userLevel, boolean hasCoupon); }apply方法负责执行具体的打折逻辑support方法负责判断当前策略是否适用于当前场景。把判断条件收进策略内部是策略模式一个很实用的设计习惯——调用方不用自己写一堆if-else去判断该用哪个策略而是问每个策略“你能不能处理这个场景”让对象自己回答。接口的命名和方法的职责一定要清晰。我见过有人把策略接口设计成只有一个doSomething()方法看起来是解耦了可调用方根本不知道这个策略是干嘛的维护成本反而更高。建议策略接口的名字直接表达业务意图比如DiscountStrategy、PaymentStrategy、NotifyStrategy方法名也要具体。2.2 具体策略把分支拆成类具体策略实现类就是原来if-else里面的每一个分支现在被拆成独立的类。VIP折扣是一个类满减优惠券是另一个类每个类只负责自己这一条规则。public class VipDiscountStrategy implements DiscountStrategy { Override public BigDecimal apply(BigDecimal originalAmount) { return originalAmount.multiply(new BigDecimal(0.9)); } Override public boolean support(String userLevel, boolean hasCoupon) { return VIP.equals(userLevel); } }public class FullReductionStrategy implements DiscountStrategy { Override public BigDecimal apply(BigDecimal originalAmount) { if (originalAmount.compareTo(new BigDecimal(100)) 0) { return originalAmount.subtract(new BigDecimal(20)); } return originalAmount; } Override public boolean support(String userLevel, boolean hasCoupon) { return hasCoupon; } }每个策略都是自包含的它知道自己支持什么条件也知道自己怎么计算。这样带来的直接好处是修改折扣规则时不需要打开其他类测试的时候也可以只测这一个类回归范围大幅缩小。2.3 上下文决定谁来算上下文是策略模式的容器它持有策略的引用并在运行时选择合适的策略去执行。最简单的上下文可以长这样public class DiscountContext { private final ListDiscountStrategy strategies; public DiscountContext(ListDiscountStrategy strategies) { this.strategies strategies; } public BigDecimal calculate(BigDecimal amount, String userLevel, boolean hasCoupon) { BigDecimal result amount; for (DiscountStrategy strategy : strategies) { if (strategy.support(userLevel, hasCoupon)) { result strategy.apply(result); } } if (result.compareTo(BigDecimal.ZERO) 0) { result BigDecimal.ZERO; } return result; } }这里的遍历顺序就是折扣的叠加顺序。VIP折扣先执行得到折后价再把折后价传给满减策略判断是否满足条件。这个设计解决了一个之前很难处理的问题多个优惠规则同时生效时它们之间的先后顺序和执行结果如何组合。2.4 上下文为什么持有接口而不是实现类这是策略模式最关键的一个设计决定值得多说一点。上下文持有的是DiscountStrategy接口类型而不是某个具体的类目的就是让上下文只和抽象打交道不知道也不关心具体的策略实现是谁。策略替换不影响上下文今天用VipDiscountStrategy明天换成SuperVipDiscountStrategy只要实现了同一个接口上下文代码一行都不用动。新增策略不影响稳定代码新增策略类是扩展不是修改。按照开闭原则稳定模块应该对扩展开放、对修改关闭。依赖方向反转上下文不再依赖具体实现而是依赖抽象具体实现反过来依赖接口。这种依赖倒置让整个模块的耦合度降得非常低。如果上下文里直接写死VipDiscountStrategy那和用if-else没有本质区别只是把分支从方法体挪到了构造函数里。2.5 调用方视角变化被隔离在哪里使用方调用上下文时的感觉是这样的ListDiscountStrategy strategies List.of( new VipDiscountStrategy(), new FullReductionStrategy() ); DiscountContext context new DiscountContext(strategies); BigDecimal finalPrice context.calculate(new BigDecimal(150), VIP, true);调用方不需要知道规则是怎么组合的也不需要在业务代码里写任何if-else。以后要加一条“新用户首单立减5元”只需要再写一个NewUserDiscountStrategy实现类然后加进策略列表即可。主流程完全不变这就是策略模式带来的隔离感。3. 从“可用”到“好用”消灭选择逻辑的三种工程写法上一章的上下文已经有了策略模式的雏形但工程上还有一个问题没有解决策略对象怎么组装如果每次都在调用方手动new出各种实现类那装配逻辑还是散落在各处搞不好又会写出几段重复的if-else。3.1 手动挑选策略的痛最原始的做法是在调用方用if-else判断用户类型再new对应的策略对象。虽然计算逻辑已经被封装进策略类了但选择逻辑还是裸露在业务代码里DiscountStrategy strategy; if (VIP.equals(userLevel)) { strategy new VipDiscountStrategy(); } else if (hasCoupon) { strategy new FullReductionStrategy(); } else { strategy new NoDiscountStrategy(); }这样写本质上只是把问题从“计算逻辑的if-else”换成了“选择策略的if-else”。一旦策略数量变多这段选择代码又会变成新的维护痛点。所以工程上我们需要一个更集中的装配方式。3.2 方式一简单工厂加Map最常用、也最好理解的做法是做一个策略工厂用Map把策略类型和策略实例的映射关系维护在一起public class DiscountStrategyFactory { private static final MapString, DiscountStrategy STRATEGIES new HashMap(); static { STRATEGIES.put(VIP, new VipDiscountStrategy()); STRATEGIES.put(FULL_REDUCTION, new FullReductionStrategy()); STRATEGIES.put(NO_DISCOUNT, new NoDiscountStrategy()); } public static DiscountStrategy get(String type) { return STRATEGIES.get(type); } }之后调用方只需要一行DiscountStrategyFactory.get(VIP)就能拿到对应的策略对象。加新策略时只需要在静态块里多注册一行主流程和调用方都不用改。这种写法简单直接哪怕没有Spring在普通Java项目里也能用得很顺手。3.3 方式二枚举绑定策略如果策略集合是固定不变的并且数量很少可以把策略实例直接绑定到枚举常量上public enum DiscountType { VIP(new VipDiscountStrategy()), FULL_REDUCTION(new FullReductionStrategy()), NO_DISCOUNT(new NoDiscountStrategy()); private final DiscountStrategy strategy; DiscountType(DiscountStrategy strategy) { this.strategy strategy; } public DiscountStrategy getStrategy() { return strategy; } }使用时可以通过枚举做映射DiscountStrategy strategy DiscountType.valueOf(VIP).getStrategy();这种写法的优势是策略类型被枚举约束住了传错字符串会在早期就暴露。缺点是枚举里绑定策略对象会让枚举承担额外职责如果策略数量持续膨胀枚举也会变得臃肿。适合策略固定、种类少于5个的场景。3.4 方式三Spring容器注入如果项目里已经用了Spring第三种写法会更优雅利用Spring的自动注入把所有实现了策略接口的Bean收集到一个List里再转成Map。Component public class DiscountStrategyFactory { private final MapString, DiscountStrategy strategyMap; public DiscountStrategyFactory(ListDiscountStrategy strategyList) { strategyMap strategyList.stream() .collect(Collectors.toMap(DiscountStrategy::getType, Function.identity())); } public DiscountStrategy get(String type) { return strategyMap.get(type); } }前提是策略接口需要加一个getType()方法返回每个策略的唯一标识。Spring在启动时会自动收集所有DiscountStrategy接口的实现类注入到构造函数里。以后新增策略类时只要把它标记为Spring管理的Bean并保证getType()返回值唯一不需要再改任何注册代码策略就会自动出现在工厂里。这种方式在真实项目中用得最多因为它把“装配”这件事完全交给了Spring容器业务代码只需要面向DiscountStrategyFactory和DiscountContext编程。3.5 三种装配方式怎么选装配方式适用场景优点缺点简单工厂加Map普通Java项目策略数量中等直观、易上手、不依赖框架注册信息集中在一个类里类可能变胖枚举绑定策略策略固定且少类型安全读取直观枚举职责变重不适合策略频繁扩展Spring容器注入Spring项目策略数量多且会持续增加扩展最方便新增策略类即可自动注册需要Spring环境排查装配问题稍复杂我的建议是没有Spring就老老实实用Map工厂别硬套枚举有Spring并且策略实现类已经超过三个优先用容器注入。选装配方式的核心标准只有一条——新策略进来时你需要改动的地方越少越好。4. 再往底层看一层多态、开闭原则和“组合优于继承”很多讲策略模式的文章都在堆代码但很少解释为什么这个模式能成立。这一章从Java的底层机制讲清楚策略模式是怎么借助多态、开闭原则和组合关系发挥作用的。4.1 策略模式的地基是Java多态Java的多态机制允许我们写这样的代码定义一个接口类型的引用实际指向一个实现类对象调用方法时执行的是实际对象的方法。游戏规则是编译期看接口类型运行期看实际类型。策略模式正是把这个机制用到了极致——上下文里声明的是DiscountStrategy接口类型但运行时传入的可能是任意一个实现类。如果没有多态策略模式就无从谈起。你只能在代码里写死VipDiscountStrategy vipStrategy new VipDiscountStrategy()然后每换一种策略就改一次代码。多态让“接口类型引用 运行时对象替换”成为可能也让策略可以像零件一样自由装卸。4.2 开闭原则在策略模式里是怎么落地的开闭原则说的是软件实体应该对扩展开放对修改关闭。策略模式是这个原则最经典的演示案例。假设你现在有VIP和满减两种策略已经稳定上线。这时产品经理说我们要加一个“限时秒杀价”策略。你只需要做两件事新建一个SeckillDiscountStrategy实现类然后在装配处注册它Map加一行或新建一个Spring Bean。已经写好的DiscountContext、DiscountStrategyFactory、调用方代码一行都不用改。这就是“对扩展开放”新增功能通过新增代码完成这也是“对修改关闭”已有稳定代码不被触碰。每次改动都被限制在新增的类里回归风险被框在一个明确的边界内。如果你打开一个项目的版本管理记录发现策略模式加持下的模块改动基本都是“新增文件”而不是“修改已有文件”说明这个模块的长尾维护成本真的很低。4.3 为什么说“组合优于继承”如果不用策略模式我们可能会用继承来解决规则变化写一个OrderService基类然后定义VipOrderService、FullReductionOrderService等子类每个子类重写折扣计算方法。这样做的直接后果是类爆炸——每加一种规则就多一个子类而且规则之间需要组合时既要VIP折扣又要满减继承结构很难表达只能再创建组合子类类数量呈指数级增长。策略模式选择的是组合上下文持有一个策略对象作为组成部分而不去继承策略类。这种“持有”关系让运行时可以自由替换零件而且多个策略可以像流水线一样串联执行。继承表达的是“是什么”的关系组合表达的是“有什么”的关系。业务规则变化频繁时“有什么”比“是什么”灵活得多。4.4 一个很容易混淆的误区策略模式不是状态模式不少初学者会把策略模式和状态模式搞混因为它们的类结构有点相似都是用一个持有接口引用的容器在运行时切换具体实现。区别在于意图不同——状态模式的重心是“对象内部状态改变导致行为变化”状态之间会互相转移通常由状态对象自己决定下一个状态是谁策略模式的重心是“算法整体替换”策略之间彼此独立互不感知。举个例子订单状态的流转待付款到已付款到已发货适合用状态模式因为下一个状态是确定的流转关系而订单折扣规则适合用策略模式因为VIP折扣和满减之间没有“谁触发谁”的关系只是按顺序执行。搞混这两种模式的结果通常是策略类里开始写“下一个策略是谁”把系统搞成一张谁也理不清的状态网。4.5 策略模式与模板方法的关系模板方法模式也是处理“变化”的常用手段但它和策略模式的切入点完全不同。模板方法定义一个算法的骨架把其中某些步骤延迟到子类实现适合“流程固定、细节多变”的场景。策略模式则是把一个完整的算法整体替换适合“整个算法都可能变”的场景。举个例子一次订单处理的流程是固定的校验参数、计算折扣、生成订单、发送通知。如果你只想调整其中“计算折扣”这一步用模板方法很合适——把每一步定义成方法子类只重写折扣计算。如果你有多种完全不同的处理方式比如“普通下单流程”和“秒杀下单流程”每一步都可能不同那就用策略模式把整套流程封装成独立的策略对象。两者不冲突有时候还会配合使用。5. 完整实战一个订单优惠系统的策略模式落地5.1 需求说明与策略设计现在把一个更像真实项目的需求完整过一遍。需求是这样的订单系统支持多种优惠规则规则之间可以叠加叠加顺序必须可控并且后续会持续增加新规则。具体规则包括VIP用户打九折、超级VIP打八折、订单金额满100减20、新用户首单立减5元、最终金额不得低于0。其中前四条规则按固定顺序执行先算等级折扣再算满减最后算新用户立减。基于这个需求我设计了这样的策略结构public interface DiscountStrategy { String getType(); BigDecimal apply(BigDecimal originalAmount, UserOrderContext orderContext); }这里的UserOrderContext是一个简单的参数对象封装用户等级、是否为新用户、是否使用优惠券等信息。把条件参数封装成对象可以让策略接口保持稳定——以后新增规则时如果还需要其他参数只要往UserOrderContext里加字段就行不用改接口签名。5.2 策略实现代码VIP折扣和满减策略public class VipDiscountStrategy implements DiscountStrategy { Override public String getType() { return VIP; } Override public BigDecimal apply(BigDecimal originalAmount, UserOrderContext orderContext) { if (VIP.equals(orderContext.getUserLevel())) { return originalAmount.multiply(new BigDecimal(0.9)); } return originalAmount; } }public class FullReductionStrategy implements DiscountStrategy { Override public String getType() { return FULL_REDUCTION; } Override public BigDecimal apply(BigDecimal originalAmount, UserOrderContext orderContext) { if (orderContext.isUseCoupon() originalAmount.compareTo(new BigDecimal(100)) 0) { return originalAmount.subtract(new BigDecimal(20)); } return originalAmount; } }新用户立减策略和防止金额为负的“收尾策略”public class NewUserDiscountStrategy implements DiscountStrategy { Override public String getType() { return NEW_USER_FIRST_ORDER; } Override public BigDecimal apply(BigDecimal originalAmount, UserOrderContext orderContext) { if (orderContext.isNewUser()) { return originalAmount.subtract(new BigDecimal(5)); } return originalAmount; } }public class NonNegativeStrategy implements DiscountStrategy { Override public String getType() { return NON_NEGATIVE; } Override public BigDecimal apply(BigDecimal originalAmount, UserOrderContext orderContext) { return originalAmount.compareTo(BigDecimal.ZERO) 0 ? BigDecimal.ZERO : originalAmount; } }NonNegativeStrategy虽然看起来“不像一个业务规则”但把它做成策略的好处是金额保护和业务规则一样可插拔、可测试、可组合。以后如果运营要求“最低到手价不低于1元”只需要修改这一个策略类。5.3 策略工厂与演示调用这里我用Map工厂来组装策略避免依赖Spring也能跑通全流程public class DiscountStrategyFactory { private final MapString, DiscountStrategy strategyMap; public DiscountStrategyFactory(ListDiscountStrategy strategies) { this.strategyMap strategies.stream() .collect(Collectors.toMap(DiscountStrategy::getType, Function.identity())); } public DiscountStrategy get(String type) { return strategyMap.get(type); } }组装和调用ListDiscountStrategy strategies new ArrayList(); strategies.add(new VipDiscountStrategy()); strategies.add(new FullReductionStrategy()); strategies.add(new NewUserDiscountStrategy()); strategies.add(new NonNegativeStrategy()); DiscountContext context new DiscountContext(strategies); UserOrderContext order new UserOrderContext(); order.setUserLevel(SUPER_VIP); order.setUseCoupon(true); order.setNewUser(true); BigDecimal finalPrice context.calculate(new BigDecimal(150), order); // 150 - 超级VIP八折120 - 满100减20100 - 新用户减595执行顺序就是策略列表的顺序非常直观。运营人员调整叠加顺序时只需要调整strategies列表里元素的顺序。5.4 验证关键边界场景单元测试可以这样覆盖边界金额为0时全流程计算后不得出现负数。同时命中等级折扣、满减、新用户立减时叠加结果符合预期。未命中任何策略时返回原价。先折扣后满减与先满减后折扣结果可能不同测试要锁定实际使用的顺序。真实项目里叠加顺序这种细节最容易踩坑。产品经理嘴上说“按顺序叠加”但没人说得清到底先减满减还是先打折。建议把顺序写清楚、用测试锁死并且和运营确认过再放出去上线。5.5 新活动来了怎么扩展假设双十一要加一个“全场减10元”策略只需要新增一个类public class DoubleElevenStrategy implements DiscountStrategy { Override public String getType() { return DOUBLE_ELEVEN; } Override public BigDecimal apply(BigDecimal originalAmount, UserOrderContext orderContext) { return originalAmount.subtract(new BigDecimal(10)); } }然后在策略列表里加一行strategies.add(new DoubleElevenStrategy())。如果工厂是用Spring收集的连这一行都不用加直接声明成组件就会自动生效。这就是策略模式带来的日常爽感改动永远是加法不是改法。6. 小白必踩的坑无状态、单例、线程安全和“为了模式而模式”6.1 策略对象必须是无状态的这是策略模式最容易被忽略、却是最重要的工程约束。所谓无状态指的是策略对象内部不能有可变的成员变量不能保存“上一次计算的结果”不能依赖外部可变的共享数据。原因很简单——策略对象通常是单例的会被多个线程同时调用如果内部有可变状态并发环境下就会出现数据互相覆盖的问题。比如下面这种写法就是典型的反模式public class VipDiscountStrategy implements DiscountStrategy { private BigDecimal lastResult; Override public BigDecimal apply(BigDecimal amount, UserOrderContext orderContext) { this.lastResult amount.multiply(new BigDecimal(0.9)); return lastResult; } }如果两个线程同时调用applylastResult会被互相覆盖其他线程读到的可能是别人的计算结果。更隐蔽的是这种bug非常难复现因为只有特定并发时序下才会出错。正确的做法是所有计算都在方法内部完成不写入实例字段不依赖外部共享状态。6.2 多线程环境下如何安全共享策略实例既然策略要无状态那么策略对象作为单例被共享就是安全的。但要特别注意引用的外部依赖也要是线程安全的。如果策略里需要调用一个SimpleDateFormat之类的非线程安全对象即使策略本身看起来无状态也会埋雷。安全的做法是把格式转换等操作放到局部变量里创建或者使用ThreadLocal必要时直接在策略方法内new一个牺牲一点点性能换安全。对于大部分订单计算场景来说局部创建的成本完全可以忽略。6.3 别为了模式而模式我在代码评审里见过不少新人对策略模式的错误使用场景。最典型的是一个接口只有一个实现类业务逻辑也根本没有变化趋势就为了“显得专业”强行套上策略模式创建接口、创建工厂、写一大堆装配代码。结果就是代码量翻倍可读性下降维护的人一脸问号。策略模式是应对变化的手段不是银弹。判断该不该用的标准很简单这个业务领域是否真的存在多种可替换的算法或规则未来是否真的有明确的新增计划如果当前只有一种实现老老实实写一个类就行。等第二个、第三个相似实现出现时再重构成策略模式一点都不晚。6.4 Java 8之后的Lambda简化Java 8引入Lambda表达式之后策略模式的“轻量级版本”变得更灵活了。如果策略的目标只是封装一段计算逻辑而不是一个复杂的行为族完全可以用函数式接口替代抽象类。MapString, UnaryOperatorBigDecimal strategies Map.of( VIP, amount - amount.multiply(new BigDecimal(0.9)), FULL_REDUCTION, amount - amount.compareTo(new BigDecimal(100)) 0 ? amount.subtract(new BigDecimal(20)) : amount );调用时直接从Map里取出UnaryOperator执行即可。这种方式适合规则简单、不需要额外方法、不需要复杂状态的场景。当策略内部逻辑变复杂或者需要多个方法协作时还是回归到经典策略类更清晰。轻量版和经典版不是对立关系而是同一个思路在不同复杂度下的两种表达。6.5 策略类膨胀时的治理经验策略模式用久了另一个烦恼是策略类太多。十几个类每个都很短小看起来零碎。我的经验是用统一的命名规范如XxxStrategy把策略类放到独立包中配合工厂统一装配不要让策略类散落到业务代码里。也可以按业务域分成多个子工厂比如OrderDiscountStrategyFactory、PaymentStrategyFactory不要让一个工厂无限膨胀。如果一群策略类只是“类似功能的参数差异”可以考虑使用模板方法抽取公共骨架再用参数或配置去区分细微差别。策略模式和模板方法模式可以组合不是二选一的关系。7. 策略模式、模板方法、if-else到底怎么选才对7.1 三种方案的适用场景对比方案适用场景新增规则时的改动风险控制if-else分支少、稳定、几乎没有扩展预期直接改原方法改动大影响全流程策略模式算法族可替换、规则多、新增频繁新增策略类加注册改动隔离只动新增模块模板方法流程骨架固定、部分步骤可变新增子类重写步骤可复用骨架但子类增多也有成本如果只有三五个分支并且一年都不怎么变用if-else一点问题都没有。设计模式不是用来炫技的是用来控制变化风险的。当变化频繁、扩展预期明确、分支逻辑可以独立成单元时才值得引入策略模式。模板方法则适用于“流程不变、步骤变”的场景比如订单处理流程固定但折扣计算步骤不同。7.2 如果选择策略模式我的常规做法如果已经决定用策略模式我会这样组织代码定义策略接口方法尽量少职责尽量清晰。每个策略类只做一件事类名直接表达规则。策略对象保持无状态计算过程全部在方法内完成。用工厂封装装配逻辑调用方永远面向工厂和上下文体编程。组合顺序显式配置必要时用测试锁定顺序。预留扩展点新策略进来时只需要新增类。这套做法适用于订单优惠、支付渠道、消息通知、数据校验、文件导出等很多Java业务领域。核心思路是一致的把变化封装到独立对象里让稳定代码不与变化逻辑纠缠。7.3 什么时候真的不适合用策略模式有些场景即便满足“有多种规则”也不一定值得用策略模式。比如规则之间的耦合度极高一个规则的计算要依赖另一个规则执行的中间状态这种情况下把它们拆成独立的策略类反而会引入大量的上下文传递代码变得别扭。再比如规则数量少到可以在一屏内展示完用策略模式带来的间接性可能大于收益。还有一点容易被忽视如果一个策略类内部逻辑超过两百行说明这个策略本身就在做太多事情。这时候要做的是把策略类继续拆分成更小的职责单元而不是为了让策略模式“成立”而强行塞逻辑。策略模式的结构只是让变化点清晰并不解决“单个类过于复杂”的问题。最后再分享一点我自己项目里的实操体会我在真实项目里使用策略模式时最深的体会是它的价值不在第一版而在第三、第四个需求到来的时候。第一版通常只有一两个规则用策略模式看不出明显优势甚至显得繁琐。等到第四个规则加进来再回头看当初花十几分钟搭的框架才发现后面的每一次改动都省下了几十分钟的排查和回归时间。有一个小技巧很实用重构的时候不要一次性把所有分支都搬进策略类。先挑出最稳定、最独立的一个规则搬进去跑通测试验证流程没有变化再逐步迁移剩下的规则。这样可以控制每次提交的粒度出问题也容易定位。另外策略类的命名我会刻意带上业务词汇比如FullReductionStrategy而不是StrategyA半年后再看代码不用打开文件就知道这个类是干什么的。如果你手头正好有一个被if-else拖累的模块不妨先画出当前所有分支标出哪些是互斥的、哪些是叠加的。只要出现“同样一个动作有多个做法、而且还在不断增加”的迹象就是引入策略模式的最佳时机。从一个最简单的策略接口开始慢慢拆稳稳测剩下的交给时间。