策略模式实战:重构支付代码告别if-else
1. 别再写if-else了一个支付选择逻辑把我逼到了重构边缘先说一个真实经历。某电商项目的订单结算模块最初只支持一种支付方式账户余额。代码很干净一个payService.pay()方法内部调余额扣减完事。需求方说加一个银行卡支付好我加了if (payType BANK_CARD)。后来又加积分抵扣、优惠券组合、钱包代扣于是这个方法越写越长。到第三个月打开这个类已经有200多行遍布着if (payType.equals(...))和switch嵌套每次发版前改这里都提心吊胆。最讽刺的是产品经理说“只是加一种新方式应该很快”可我却要花大半天梳理旧逻辑生怕漏掉某个分支没有处理金额分摊。于是我去老老实实学习了策略模式Strategy Pattern。刚开始觉得它只是教科书里的概念直到亲手把这段“瘤子代码”重构成策略模式后我意识到如果你也写过那种“不敢动、动一点就崩”的条件分支代码那策略模式是你最值得先掌握的设计模式之一。这篇文章不打算堆概念我带你从头写一个支付渠道接入的真实案例顺便把我的理解、避坑经验、测试方法都摊开讲。1.1 那段从20行长到200行的“瘤子代码”先还原一下最初的“原始代码”长什么样方便你对照后面的重构。我简化成伪业务逻辑public PayResult pay(String payType, BigDecimal amount, Long userId) { if (BALANCE.equals(payType)) { // 1. 校验余额 BigDecimal balance balanceService.getBalance(userId); if (balance.compareTo(amount) 0) { return PayResult.fail(余额不足); } // 2. 扣减余额 balanceService.decrease(userId, amount); // 3. 记录流水 return PayResult.success(余额支付成功); } else if (POINTS.equals(payType)) { BigDecimal points pointsService.getPoints(userId); if (points.compareTo(amount) 0) { return PayResult.fail(积分不足); } pointsService.deduct(userId, amount); return PayResult.success(积分支付成功); } else if (BANK_CARD.equals(payType)) { // 校验银行卡、调用银行网关、处理回调…… return bankCardService.charge(userId, amount); } return PayResult.fail(未知支付方式); }看起来还不算特别糟对吗但真实项目里这个pay方法里还会掺入优惠券分摊、折扣计算、风控校验、流水记录、订单状态流转。到第三周else if已经有5个方法里穿插了十几个临时变量有人为了复用逻辑还往里面塞了一个“公共处理方法”。别说新同事看不懂连我自己都开始害怕改它。这种代码最大的问题是每多一种支付方式就要动一次老方法。老方法里每一个分支都可能和其他分支共享同一个局部变量、同一个校验逻辑牵一发而动全身。它违背了面向对象设计里最基本的“开闭原则”——对扩展开放对修改关闭。1.2 条件分支的四个痛点戳中你了吗经常有人问难道写if-else就十恶不赦吗不是简单场景用if-else完全没问题。但当分支数量超过三个并且每个分支都有自己的一套“算法步骤”时它就会出现四个逃不掉的痛点可读性差方法和类越来越长读代码要从第一个分支follow到最后一个中途还会被各种状态变量带偏。扩展性差新增逻辑必须先读懂全部旧逻辑。哪怕你的本意只是加一条新分支也难保不会影响其他分支。可测试性差要覆盖所有支付方式的组合你得构造大量分支条件单元测试里全是if的迷宫测试代码比业务代码还难维护。惯性耦合所有逻辑挤在一起A支付方式经常会不小心复用B支付方式里的临时变量久而久之代码架构变成了一锅粥。策略模式解决这四件事的思路特别朴素把每一种支付方式看成一种“算法”各自封装成一个独立类让它们实现同一个接口。调用方只需要面向接口不关心具体实现是谁。这样“加新支付方式”就变成了“加一个新类”老代码一行都不用动。1.3 策略模式的思路让每种玩法自己站队用一个生活化的类比。你开了一家餐厅门口接待顾客的服务员接到订单时不再自己进厨房做菜而是根据菜品类型把订单分发给不同厨师凉菜师傅、热菜师傅、面点师傅。每个师傅只负责自己的品类新菜上市只需聘请一个新师傅或者把新菜派给已有师傅接待员的工作流程永远不变。在这个例子里服务员相当于“上下文”菜品类目相当于“策略接口”每个厨师就是具体的策略类。对应到代码里策略模式的常规结构是这样三件套策略接口定义一组可替换的行为例如“支付动作”具体策略类实现接口各自封装一套完整的业务算法上下文类持有策略接口的引用负责调用策略不关心策略内部怎么实现。这种结构把“做什么”策略和“谁来做”具体类分开也和调用方解耦。下面我用一个小案例把它讲明白。2. 认清策略模式手里的三个角色代码自然就会写了很多小白看设计模式的UML图第一反应是“我能看懂图但不知道代码怎么组织”。这里我换个方式不画复杂的UML直接按角色一个一个写给你看。2.1 策略接口先定义“能干什么”首先要有一个公共接口相当于给所有策略类“挂牌”。支付场景下策略接口可以这样定义public interface PayStrategy { PayResult pay(PayRequest request); }其中PayRequest是支付请求参数我把订单号、用户ID、金额、支付方式等信息都放进去public class PayRequest { private Long orderId; private Long userId; private BigDecimal amount; // 构造函数、getter、setter省略 }PayResult是统一返回结果至少包含成功标记、消息、交易流水号public class PayResult { private boolean success; private String message; private String tradeNo; public static PayResult success(String message, String tradeNo) { return new PayResult(true, message, tradeNo); } public static PayResult fail(String message) { return new PayResult(false, message, null); } // 其余代码省略 }接口的意义在于它定下了一条规则——不管你是哪种支付方式对外都只有一个统一的动作pay(request)。调用方不需要知道银行卡怎么调网关、余额怎么扣减、积分怎么换算只需要知道“我调用这个动作它会给我一个结果”。2.2 具体策略类不同实现各显神通接着为每一种支付方式写一个策略类。比如余额支付public class BalancePayStrategy implements PayStrategy { private final BalanceService balanceService; public BalancePayStrategy(BalanceService balanceService) { this.balanceService balanceService; } Override public PayResult pay(PayRequest request) { BigDecimal balance balanceService.getBalance(request.getUserId()); if (balance.compareTo(request.getAmount()) 0) { return PayResult.fail(余额不足); } balanceService.decrease(request.getUserId(), request.getAmount()); String tradeNo generateTradeNo(); return PayResult.success(余额支付成功, tradeNo); } }再写一个积分支付public class PointsPayStrategy implements PayStrategy { private final PointsService pointsService; public PointsPayStrategy(PointsService pointsService) { this.pointsService pointsService; } Override public PayResult pay(PayRequest request) { BigDecimal points pointsService.getPoints(request.getUserId()); if (points.compareTo(request.getAmount()) 0) { return PayResult.fail(积分不足); } pointsService.deduct(request.getUserId(), request.getAmount()); return PayResult.success(积分支付成功, generateTradeNo()); } }可以看到每个策略类就是一个“算法封装体”互不干扰。将来要新增银行卡、钱包、线下扫码等都是写一个新类实现同一个接口。2.3 上下文类不直接new而是稳稳接过策略光有策略类还不够调用方需要一个“壳”来接收策略。这个壳在策略模式里叫上下文Context它的核心职责是保存一个策略引用并在合适的时候执行它。最简单的上下文如下public class PayContext { private PayStrategy payStrategy; public PayContext(PayStrategy payStrategy) { this.payStrategy payStrategy; } public void setPayStrategy(PayStrategy payStrategy) { this.payStrategy payStrategy; } public PayResult executeStrategy(PayRequest request) { // 上下文可以做一些共性校验、埋点、统计 return payStrategy.pay(request); } }有人会问为什么客户端不直接new BalancePayStrategy()然后调用pay()因为那样你就绕过了“统一入口”的概念。上下文的价值在于它可以集中处理所有策略都需要的公共逻辑比如参数校验、埋点日志、性能统计、事务包装。你可以在executeStrategy里把这些公共逻辑放进去而具体策略只关心自己的差异化步骤。另一个价值是可替换性。调用方可以在运行时通过setPayStrategy改变策略这是策略模式很灵活的一个体现。当然如果你的策略不需要中途替换构造器注入就够用了。2.4 一个能跑的最小示例看完就会为了让你确定“我懂了”我写一个最迷你的完整Demo。没有Spring没有数据库只有策略模式和main方法。public class StrategyDemo { public static void main(String[] args) { // 策略1余额支付mock一个服务 BalanceService balanceService new BalanceService(); PayStrategy balanceStrategy new BalancePayStrategy(balanceService); // 放入上下文 PayContext context new PayContext(balanceStrategy); // 构造请求 PayRequest request new PayRequest(1001L, 888L, new BigDecimal(99.00)); // 通过上下文执行策略 PayResult result context.executeStrategy(request); System.out.println(result.getMessage()); } }这段代码跑起来很简单创建一个具体的策略塞给上下文调用统一执行方法。后续想换成积分支付只需换一行创建策略的代码上下文和调用代码完全不用改。3. 手写真实案例把支付方式改成策略模式现在我们把前面的“瘤子代码”彻底重构成策略模式。这个案例很接近真实项目我会按步骤带你走一遍。3.1 定义支付策略接口与统一返回结构第一步还是定义接口。不过真实项目里支付动作通常要做的事情不止“扣钱”往往还包括预校验、业务处理、结果落库。为了保持清晰我把策略接口设计成更完整的样子public interface PayStrategy { PayStrategyType getType(); // 策略类型标识 PayResult pay(PayRequest request); // 执行支付 boolean validate(PayRequest request); // 差异化校验 }PayStrategyType是一个枚举用来标识每个策略public enum PayStrategyType { BALANCE, POINTS, BANK_CARD }PayRequest和PayResult沿用上一节的简版。但更贴近实战的是PayResult里可以带上更多字段例如支付时间、手续费、优惠金额等。这里保持原样。3.2 实现余额、积分、银行卡三类策略然后是三个具体策略类。为了不让代码膨胀我展示两个银行卡用一个类名示意。余额支付策略public class BalancePayStrategy implements PayStrategy { private final BalanceService balanceService; public BalancePayStrategy(BalanceService balanceService) { this.balanceService balanceService; } Override public PayStrategyType getType() { return PayStrategyType.BALANCE; } Override public boolean validate(PayRequest request) { return balanceService.getBalance(request.getUserId()) .compareTo(request.getAmount()) 0; } Override public PayResult pay(PayRequest request) { if (!validate(request)) { return PayResult.fail(余额不足); } balanceService.decrease(request.getUserId(), request.getAmount()); return PayResult.success(余额支付成功, generateTradeNo()); } private String generateTradeNo() { return BAL System.currentTimeMillis(); } }积分支付策略结构完全相同只是把余额服务换成积分服务。不再重复贴完整代码关键点都一样getType()返回自己的类型validate()做校验pay()执行核心逻辑。银行卡支付策略稍微复杂因为要处理外部网关调用但对外依然是同一套接口。示意如下public class BankCardPayStrategy implements PayStrategy { private final BankCardService bankCardService; // 构造函数省略 Override public PayStrategyType getType() { return PayStrategyType.BANK_CARD; } Override public boolean validate(PayRequest request) { return bankCardService.validateCard(request.getUserId()); } Override public PayResult pay(PayRequest request) { if (!validate(request)) { return PayResult.fail(银行卡校验失败); } return bankCardService.charge(request.getUserId(), request.getAmount()); } }到这里你是否有感觉了每个策略类负责一件独立的事内部怎么折腾都不影响别人。3.3 重构服务层用策略替换条件分支现在关键来了原来那个200行的pay方法怎么改最直接的方式是把策略放进一个Map里根据类型取出对应策略。public class PayService { private final MapPayStrategyType, PayStrategy strategyMap; public PayService(ListPayStrategy strategyList) { strategyMap strategyList.stream() .collect(Collectors.toMap( PayStrategy::getType, Function.identity() )); } public PayResult pay(PayRequest request, PayStrategyType type) { PayStrategy strategy strategyMap.get(type); if (strategy null) { return PayResult.fail(不支持的支付方式); } return strategy.pay(request); } }你看pay()方法里再也没有if-else和switch了。PayService初始化时把所有策略装进Map支付时直接按类型查表。这个Map本质上就是一个策略注册表这也是策略模式在实际项目里最常见的落地方式之一。如果你不想暴露PayStrategyType给客户端可以让PayRequest里带上支付方式字段PayService内部自己解析。但设计上建议显式传类型因为策略模式的重点是“调用方知道他要的策略类型”而不是让服务瞎猜。3.4 客户端如何调用扩展新支付方式有多爽客户端调用重构后的服务长这样RestController public class OrderController { private final PayService payService; PostMapping(/pay) public PayResult pay(RequestBody PayRequest request) { return payService.pay(request, request.getPayType()); } }代码至少比以前清爽一个量级。更重要的是扩展性现在产品说要加“钱包零钱支付”你要做的事有三件定义WalletPayStrategy实现PayStrategy接口写好钱包扣款逻辑把这个类注册到Spring容器里如果使用Spring会自动被注入到strategyList中。完事。PayService不用改一行代码OrderController也不用改。这才是面向对象设计的魅力——你在“加功能”而不是“改老代码”。当时我重构完这个支付模块后产品再提新支付方式我的交付时间从“半天看代码”变成了“半小时写新类”幸福感直线上升。4. 策略模式的高级玩法注册表、动态切换与函数式简化上面的案例已经算是“入门到实战”了但真实的项目里策略模式常常会和依赖注入、枚举、Lambda组合出更强大的玩法。这一节我把工作中常用到的几个进阶姿势分享给你。4.1 策略注册表不用写满屏if-else也能找到策略你已经看到了用Map类型, 策略保存所有策略本质就是一张“策略注册表”。这张表的构建方式可以很灵活手动put适合策略数量少、不依赖Spring的项目通过Spring的List自动注入后转Map适合大多数Web项目通过注解扫描在启动时把带有某个注解的类注册进去适合插件式系统。策略注册表的好处是把“找策略”从散落的if-else收敛成“查字典”。它没有完全消灭条件选择只是把“选择逻辑”集中到一张表里让代码意图更清晰。如果发现新策略加入时还要改表那说明你想要的可能是“零配置扩展”可以配合Spring等框架的自动装配新策略类一放进容器就自动注册。4.2 结合Spring容器通过Map批量注入策略在Spring Boot项目里依赖注入会自动帮你完成绝大部分工作。做法非常简单在构造器里接收ListPayStrategy然后转成Map。Service public class PayStrategyRouter { private final MapString, PayStrategy strategyMap; public PayStrategyRouter(ListPayStrategy strategies) { this.strategyMap strategies.stream() .collect(Collectors.toMap(PayStrategy::getType, strategy - strategy)); } public PayResult route(String type, PayRequest request) { PayStrategy strategy strategyMap.get(type); if (strategy null) { throw new IllegalArgumentException(Unsupported strategy: type); } return strategy.pay(request); } }Spring会把所有实现了PayStrategy接口的bean自动收集到ListPayStrategy里。之后你新增一个策略类只需加上Component它就会自动出现在Map中。这就是“开闭”的终极体现新增策略时连集中注册的代码都不用碰。这个模式我几乎每天都在用不光是支付还包括优惠计算、消息发送、数据导入导出。只要是一组可替换的算法行为都能这样组织。4.3 运行时动态切换策略的思路策略模式最迷人的地方在于策略可以在运行时被替换。举个例子一个订单在结算时系统需要根据订单金额和用户等级自动决定采用哪种“营销折扣策略”——满减策略、折扣策略、立减策略。注意这不是用if-else去选而是动态算出策略再交给上下文执行。public BigDecimal calculateDiscount(Order order) { PayStrategy strategy discountStrategyFactory.getDiscountStrategy(order); context.setStrategy(strategy); return context.execute(order); }在支付场景里你也可以先走“零钱代扣”如果零钱不足自动切换“银行卡代扣”。只要策略之间没有副作用这种动态切换非常优雅。但要注意如果上下文对象被多个线程共享setStrategy会带来线程安全问题。下一篇我讲状态模式时会展开这里你先记住动态切换策略是特性但不要滥用尤其不要在多线程共享的上下文里随便切换。4.4 用枚举和lambda干掉“策略类爆炸”策略模式的一个副作用是类数量膨胀。如果一个系统里动态策略有20个每个类只有几行逻辑光建文件和命名就很烦。幸好Java 8之后我们可以用枚举函数式接口来简化。假如我们的策略逻辑不依赖外部服务只是根据请求类型返回不同结果可以这样写public enum PayStrategyEnum { BALANCE(BALANCE, request - { return PayResult.success(余额支付, BAL System.nanoTime()); }), POINTS(POINTS, request - { return PayResult.success(积分支付, POI System.nanoTime()); }); private final String type; private final FunctionPayRequest, PayResult handler; PayStrategyEnum(String type, FunctionPayRequest, PayResult handler) { this.type type; this.handler handler; } public static PayResult pay(String type, PayRequest request) { for (PayStrategyEnum strategy : values()) { if (strategy.type.equals(type)) { return strategy.handler.apply(request); } } throw new IllegalArgumentException(Unsupported type: type); } }这个方案非常轻量适合那些“策略类只有一段简单逻辑、无状态、不需要依赖外部服务”的场景。但如果你发现策略实现开始依赖复杂的Service或者逻辑超过了十几行就老实拆成类吧。优雅和可维护之间需要平衡。5. 策略模式和它的“近亲”们状态、模板方法、工厂设计模式的难点不光是“怎么用”更是“什么时候该用哪一个”。策略模式经常被拿来和状态模式、模板方法模式、工厂模式对比我帮你理一理。5.1 策略模式vs状态模式一个选行为一个选身份这两个模式在结构上很像都是一个上下文持有某个接口引用并且可以被替换。但它们的关注点完全不同。策略模式解决的是“同一种目标多种算法”。比如支付这个目标可以用余额、积分、银行卡去实现策略之间是“平等可替换”的调用方主动选择谁。状态模式解决的是“由于内部状态发生变化行为也要跟着变”。比如订单状态待支付、已支付、已取消、已退款每个状态下执行的操作不同而且状态之间是有转移规则的——待支付可以变成已支付已支付可以变成已取消少见但不能从已退款变回已支付。状态模式强调的是“内在状态的转移导致行为自动切换”。维度策略模式状态模式核心目的封装可替换算法封装随状态变化的行为状态转移不关心通常由外部调用方决定切换策略内部定义状态转移逻辑代码结构上下文持有策略接口上下文持有状态接口状态内部驱动转移典型场景支付、折扣、排序订单状态机、审批流所以如果你发现“行为切换是需要下一个状态来决定的”优先考虑状态模式如果只是“同一件事有不同做法由调用方选定”用策略模式。5.2 策略模式vs模板方法模式控制权在哪里模板方法模式的核心是父类定义一个算法骨架把可变步骤抽象成方法由子类去实现。它靠继承来复用子类只能改写某些步骤算法的整体顺序仍然由父类控制。策略模式则不一样它靠组合。上下文完全不知道算法的步骤它只是委托给策略对象。你甚至可以完全替换一套算法而不需要继承任何类。举个例子。做订单结算模板方法父类定义“先结算商品、再计算优惠、最后扣除库存”子类可以改“如何计算优惠”但“先结算再优惠再扣库存”的顺序被父类焊死。策略模式定义一个结算策略接口余额结算是一种策略包含自己的步骤银行卡结算是另一种策略也可能包含完全不同的步骤顺序。选择建议如果你能明确算法骨架且大部分步骤固定只有个别步骤变化用模板方法如果你希望整体算法都能灵活替换并且不想被继承关系绑死用策略模式。5.3 策略模式和工厂模式是黄金搭档不是替代品经常有小白问策略模式里“根据类型拿策略”这不就是工厂模式吗其实这两个模式各管一段工厂负责创建对象策略负责封装行为。打个比方工厂是“根据订单类型告诉你去哪个厨师窗口排队”策略是“厨师拿到菜品需求后按照各自的菜谱去做菜”。工厂解决“对象怎么来”策略解决“对象来了之后怎么做”。在实战里两者经常一起出现。我们的PayService里那个Map其实就是简单工厂逻辑每个工厂方法返回一个具体的策略对象。Spring的ListPayStrategy自动收集也是一种“工厂策略”的组合拳。所以别把它们对立起来它们是完美搭档。6. 实战中的坑、测试方法和“不要用策略”的场景设计模式不是银弹。策略模式用得好是利器用得不好就是过度设计。这一章我讲讲自己踩到的坑、怎么给策略代码写测试以及什么情况下真的不该用策略模式。6.1 我踩过的几个策略模式坑坑一策略类里悄悄存了有状态字段。我给某个导出模块写策略时图省事在策略类里放了一个ListString logs想着用来记录导出过程中的警告。结果发现多个线程同时使用同一个策略实例logs被串了导出的用户A的数据混进了B的日志。后来改成每次调用都从外部传入一个上下文对象才彻底解决。策略类最好做成无状态的如果非要记录过程数据用方法参数传入数据容器不要用成员字段。坑二没有兜底策略。当strategyMap.get(type)返回null时直接空指针。我在一次线上问题里就是漏了“未知支付方式”的兜底客户端传了一个被下架的老支付方式系统直接500。建议注册表查询后永远加一个if (strategy null) return Fail或者抛业务异常不要让它自然穿透。坑三动态切换策略在各种异步线程里把上下文搞乱。这跟坑一类似但更隐蔽。如果上下文是单例Bean里面setStrategy之后下个请求可能还在用它。策略上下文在Web应用里尽量不要做成共享的应该是一次请求一个上下文或者在方法内部根据请求临时构建。坑四类爆炸后无家可归。策略类数量太多会让项目的包结构看起来很碎。我后来会按业务模块建strategy子包并在类命名上加上统一前缀例如PayBalanceStrategy、PayPointsStrategy让IDE里按名称排序时整整齐齐。6.2 给策略类写单元测试的正确姿势策略模式天生适合单元测试因为每个策略都是独立类可以单独验证。下面给一个JUnit 5的简单例子。class BalancePayStrategyTest { private BalanceService balanceService; private BalancePayStrategy strategy; BeforeEach void setUp() { balanceService mock(BalanceService.class); strategy new BalancePayStrategy(balanceService); } Test void shouldFailWhenBalanceNotEnough() { when(balanceService.getBalance(888L)).thenReturn(new BigDecimal(10.00)); PayRequest request new PayRequest(1L, 888L, new BigDecimal(99.00)); PayResult result strategy.pay(request); assertFalse(result.isSuccess()); assertEquals(余额不足, result.getMessage()); } Test void shouldSuccessWhenBalanceEnough() { when(balanceService.getBalance(888L)).thenReturn(new BigDecimal(100.00)); PayRequest request new PayRequest(1L, 888L, new BigDecimal(99.00)); PayResult result strategy.pay(request); assertTrue(result.isSuccess()); verify(balanceService).decrease(888L, new BigDecimal(99.00)); } }测试策略类的最大收益是你再也不用为测试实现细节而脑补一堆“前置条件”。每个策略的输入输出非常明确Mock掉外部依赖之后测的就是这个算法本身。这也是重构核心业务逻辑时的重要保障。对于PayService这种带注册表的服务测试就更简单了注入两个策略各测一次路由结果再测一次未知类型返回失败。策略模式让单元测试的覆盖率变得容易维持。6.3 什么时候别用策略模式过度设计警钟不是所有if-else都要重构。如果满足下面几个条件我建议你先别引入策略模式分支数量很少且非常稳定例如一个接口只区分“是”和“否”而且需求方明确未来不会新增分支那用if反而更直观策略内部逻辑太相似只是参数不同这时用一个方法加参数更好没必要拆类你的系统还处于原型阶段需求每天都在变先别急着抽象用if-else快速响应需求等稳定了再重构策略类数量可能会疯狂膨胀但每类又没多少可复用逻辑优先考虑枚举、Maplambda等轻量方案。设计模式不是“用得多就好”而是“该用的时候有意义”。判断信号很简单如果你觉得“加一个分支要改老代码很痛苦”那说明你有提取策略的动机如果你只是“想显得很架构”那还是先忍住。6.4 最后说点大实话怎么学设计模式最有效学策略模式或者任何设计模式我最推荐的路径不是背定义而是做三件事。第一找一段你项目里真实的、带大量if-else的业务代码亲手重构一遍。只有碰到真实参数、真实异常、真实边界你才会理解为什么策略类不能存状态、为什么要有兜底。第二每学一个模式就给它建立一个“匹配条件清单”什么信号出现时该用它、什么信号出现时不该用它。第三写完代码后花十分钟写几个单元测试用测试把策略的边界固定下来。我自己刚学策略模式时以为看懂了UML图就等于会了。结果一上手连“上下文该怎么设计”都犹豫了很久。后来踩了那几个坑总算明白设计模式是把你对业务的理解沉淀成一套可以被扩展和测试的代码结构。它没有多神秘就是一种组织代码的习惯。如果你能照着这篇文章把自己手头那个又臭又长的条件分支重构出一个最小可运行版本那策略模式你就真正入了门。等你在项目里再多遇到几种“同一件事不同做法”的场景你会发现自己已经开始本能地去想这里是不是可以抽一个策略接口到那个阶段你已经不需要再看指南了。