
策略模式告别 if-else让算法自由切换一个电商系统的支付模块往往有很多支付方式。需求很简单支持支付宝、微信、银行卡三种支付方式。你打开ide写下了这段代码publicStringpay(Stringtype,BigDecimalamount){if(alipay.equals(type)){// 调用支付宝 SDKreturn支付宝支付成功amount;}elseif(wechat.equals(type)){// 调用微信 SDKreturn微信支付成功amount;}elseif(bank.equals(type)){// 调用银行 SDKreturn银行卡支付成功amount;}else{return不支持该支付方式;}}功能是实现了但产品经理又提了需求要加 Apple Pay。你改了一次代码。过两天又说要加数字人民币。你又改了一次。再过两天说要根据用户等级给不同支付方式打不同的折扣逻辑你看着这个越长越胖的if-else陷入了沉思。每次新增一种支付方式都要回来改这个方法。改多了不仅容易出错还会和同事产生代码冲突。有没有一种方式能让每种支付方式自己管自己新增的时候只加代码不改老代码策略模式可以完美解决你的问题。目录策略模式是什么为什么需要策略模式策略模式的结构用策略模式重构上述if-else结构策略模式与 if-else 的对比策略模式的进阶用法小结策略模式是什么策略模式是一种行为型设计模式它把一组算法各自封装成独立的对象让它们可以互相替换。好比你去餐厅吃饭菜单上有清蒸、红烧、水煮三种做法你选了红烧鱼。你不需要知道红烧的具体步骤你只需要告诉服务员我要红烧厨房就按照红烧的流程来做。下次你想换口味说改成清蒸厨房就切换到清蒸的流程。做法算法是可替换的点菜的流程调用方不需要变。策略模式做的就是这件事把每种做法封装成一个策略对象调用方只需要选策略不用关心具体怎么执行。为什么需要策略模式回到开头那个支付场景。用if-else写的代码有三个明显的问题1. 违反开闭原则。每新增一种支付方式都要修改pay方法。开放了修改而不是开放了扩展。2. 职责不清晰。所有的支付逻辑堆砌在一个方法里支付宝的 、微信的、银行的调用逻辑全混在一起一点也不“优雅”。3. 难以复用。如果另一个模块也需要根据支付类型执行不同逻辑你得把这段if-else再抄一遍或者抽成一个更庞大的方法。策略模式的解法是把每个分支变成一个独立的类通过一个统一的接口调用。新增支付方式就新增一个类已有的代码一行也不用动。策略模式的结构先看整体结构只有三个角色Context上下文 │ │ 持有 ▼ Strategy策略接口 │ ├── ConcreteStrategyA具体策略 A ├── ConcreteStrategyB具体策略 B └── ConcreteStrategyC具体策略 C角色职责Strategy策略接口定义所有策略的共同行为规范ConcreteStrategy具体策略实现具体的算法逻辑Context上下文持有策略引用委托策略执行为什么中间还要加一个 Context直接让调用方new AlipayStrategy().pay(amount)可不可以确实是可以的但 Context 的存在有两个意义。第一它封装了策略的切换逻辑调用方不需要每次都手动创建策略实例只需要context.setStrategy(...)一个方法搞定。第二它可以持有公共状态。比如支付场景中Context 可以保存用户的订单号、支付金额等信息策略执行时从 Context 中获取而不是每个策略都传一遍这些参数。策略接口定义了能做什么Context 决定了让谁来做具体策略负责怎么做。三者各司其职。用策略模式重构上述 if - else结构第一步定义策略接口所有支付方式都要实现同一个接口这是策略模式的契约// 支付策略接口publicinterfacePayStrategy{Stringpay(BigDecimalamount);}接口只定义一个方法pay所有的支付方式都去实现它。调用方只需要认这个接口不需要知道具体是哪种支付。这里有一个设计上的取舍接口方法越少越好。一个方法的接口意味着每个策略只需要做一件事职责单一理解和实现的成本都很低。如果你的接口有五六个方法那大概率是拆得不够细或者把不该属于策略的东西塞进来了。第二步实现具体策略每种支付方式是一个独立的类各自管各自的逻辑// 支付宝支付策略publicclassAlipayStrategyimplementsPayStrategy{OverridepublicStringpay(BigDecimalamount){// 调用支付宝 SDK 的逻辑return支付宝支付成功amount;}}// 微信支付策略publicclassWechatPayStrategyimplementsPayStrategy{OverridepublicStringpay(BigDecimalamount){// 调用微信 SDK 的逻辑return微信支付成功amount;}}// 银行卡支付策略publicclassBankPayStrategyimplementsPayStrategy{OverridepublicStringpay(BigDecimalamount){// 调用银行 SDK 的逻辑return银行卡支付成功amount;}}每个类只关心自己的支付逻辑。支付宝的类并不知道微信的存在微信的类也不知道银行的存在。它们之间完全解耦。注意一个细节每个策略类内部的实现可以完全不同。支付宝可能需要处理 RSA 签名微信可能需要处理 HMAC 签名银行可能需要调用银联的 SOAP 接口。这些复杂的差异被封装在各自的类里对外只暴露一个统一的pay方法。调用方永远不需要知道支付宝支付和微信支付到底有什么区别它只知道传入金额拿到结果。第三步实现上下文Context 是调用方和策略之间的桥梁。它持有策略引用把具体的支付操作委托给策略执行// 支付上下文publicclassPayContext{privatePayStrategystrategy;// 设置支付策略publicvoidsetStrategy(PayStrategystrategy){this.strategystrategy;}// 执行支付publicStringexecutePay(BigDecimalamount){if(strategynull){thrownewRuntimeException(请先选择支付方式);}returnstrategy.pay(amount);}}setStrategy让你可以运行时切换策略executePay委托给具体的策略执行。Context 不知道也不关心具体用的是哪个策略。你可以把 Context 理解成一个遥控器。遥控器上只有一个播放按钮executePay但你插不同的光盘策略按播放就会播放不同的内容。遥控器本身不需要改换光盘就行。第四步使用publicclassMain{publicstaticvoidmain(String[]args){PayContextcontextnewPayContext();// 用户选择支付宝context.setStrategy(newAlipayStrategy());System.out.println(context.executePay(newBigDecimal(99.9)));// 输出: 支付宝支付成功99.9// 用户切换成微信context.setStrategy(newWechatPayStrategy());System.out.println(context.executePay(newBigDecimal(199.0)));// 输出: 微信支付成功199.0// 再切换成银行卡context.setStrategy(newBankPayStrategy());System.out.println(context.executePay(newBigDecimal(50.0)));// 输出: 银行卡支付成功50.0}}调用方只需要做两件事选策略、执行。至于每种支付方式内部怎么实现的调用方完全不关心。这就是策略模式最直观的好处调用方的代码不会因为策略的增加而膨胀。不管是 3 种支付方式还是 30 种Main里的代码结构都是一样的只是setStrategy传入的对象不同。现在要新增 Apple Pay怎么做只需要加一个类publicclassApplePayStrategyimplementsPayStrategy{OverridepublicStringpay(BigDecimalamount){returnApple Pay支付成功amount;}}然后在使用的地方context.setStrategy(new ApplePayStrategy())就行了。已有代码一行没改。这就是开闭原则的体现对扩展开放对修改关闭。整个实现的核心机制可以总结为三步定义接口、封装策略、运行时切换。策略接口定义了做什么具体策略各自实现怎么做上下文负责选谁来做。理解上述策略模式模板的难点在于setStrategy(PayStrategy strategy)这个方法。它的意义不只是赋值还可以让策略可以在运行时动态切换。这是策略模式和if-else的本质区别if-else的分支在代码写死的那一刻就确定了而策略模式的切换发生在运行时。策略模式与 if-else 的对比对比维度if-else策略模式新增策略改已有方法加 else if新建一个类实现接口删除策略删 else if可能影响后续逻辑删掉对应的类不影响其他切换方式编译时写死运行时动态切换职责边界全堆在一个方法里每个策略独立各管各的可测试性难以单独测试某个分支每个策略可以独立单元测试代码冲突改同一个方法容易冲突各写各的类互不干扰if-else 是编译时写死的逻辑分支策略模式是运行时可替换的对象。再举一个实际的差异。假设你的系统需要支持 A/B 测试新用户看到新版支付流程老用户看到旧版。用if-else你得在pay方法里再套一层判断逻辑越来越复杂。用策略模式你只需要在运行时根据用户类型选择不同的策略if(user.isNewUser()){context.setStrategy(newNewVersionPayStrategy());}else{context.setStrategy(newOldVersionPayStrategy());}策略的选择逻辑和策略的实现逻辑是分开的各自独立演化互不干扰。还有一个容易忽略的点可测试性。用if-else写的支付逻辑你想单独测试支付宝的分支得构造一个type alipay的条件还得确保不会误入其他分支。而策略模式下每个策略类就是一个独立的单元直接new AlipayStrategy().pay(amount)就能测试不需要任何前置条件。单元测试写起来简单跑起来也快。策略模式的进阶用法用工厂封装策略选择虽然PayContext里的if-else消除了但在使用的地方还是要手动new具体策略。如果策略很多调用方还是得知道所有策略类的名字。这时候可以配合工厂模式把策略的创建也封装起来importjava.util.HashMap;importjava.util.Map;// 策略工厂publicclassPayStrategyFactory{privatestaticfinalMapString,PayStrategySTRATEGIESnewHashMap();// 注册所有策略static{STRATEGIES.put(alipay,newAlipayStrategy());STRATEGIES.put(wechat,newWechatPayStrategy());STRATEGIES.put(bank,newBankPayStrategy());}// 根据类型获取策略publicstaticPayStrategygetStrategy(Stringtype){PayStrategystrategySTRATEGIES.get(type);if(strategynull){thrownewRuntimeException(不支持的支付方式type);}returnstrategy;}}使用的时候就更简洁了PayContextcontextnewPayContext();context.setStrategy(PayStrategyFactory.getStrategy(alipay));context.executePay(newBigDecimal(99.9));调用方只需要传一个字符串alipay不需要知道AlipayStrategy这个类的存在。新增支付方式只需要在工厂里注册一行和之前用TOOL_REGISTRY注册工具是一个思路。这个工厂本质上就是一个注册表字符串到策略对象的映射。和我们之前在 Agent 工具注册那篇文章里讲的TOOL_REGISTRY是同一个模式。你会发现设计模式之间是相通的策略模式 工厂模式的组合在实际项目中非常常见几乎是标配。用 Spring 自动注入策略如果你用 Spring 框架策略模式可以用的更方便更优雅。Spring 的Autowired支持按名称注入可以自动把所有策略实现收集到一个 Map 里importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importjava.util.Map;ComponentpublicclassPayStrategyFactory{privatefinalMapString,PayStrategystrategyMap;// Spring 会自动把所有 PayStrategy 实现注入进来// key 是 Bean 的名称value 是对应的实例AutowiredpublicPayStrategyFactory(MapString,PayStrategystrategyMap){this.strategyMapstrategyMap;}publicPayStrategygetStrategy(Stringtype){returnstrategyMap.get(type);}}具体策略只需要加上Component注解并指定名称Component(alipay)publicclassAlipayStrategyimplementsPayStrategy{OverridepublicStringpay(BigDecimalamount){return支付宝支付成功amount;}}Component(wechat)publicclassWechatPayStrategyimplementsPayStrategy{OverridepublicStringpay(BigDecimalamount){return微信支付成功amount;}}新增支付方式只需要加一个Component类连工厂的注册代码都不用改。Spring 帮你完成了策略的注册和查找这才是策略模式在实际项目中最常见的用法。为什么 Spring 的方式更优雅因为手动注册工厂有个隐患你加了新的策略类但忘了在工厂里注册编译不会报错只有运行时才会发现找不到策略。而 Spring 的自动注入消除了这个问题只要你的类实现了接口并加了Component就一定会被注入进来不会遗漏。让框架帮你兜底比靠人脑记忆可靠得多。小结策略模式本质上在做一件事把选择哪个算法和算法怎么执行分离开来。算法之间各自封装、互不干扰选择发生在运行时而不是编译时。如果代码里出现了越来越多的if-else分支每个分支对应一种算法或行为而且这些分支还在不断新增不妨试一试策略模式。先抽出策略接口再把每个分支变成一个具体策略类最后用上下文类来管理和切换策略这样就成功用策略模式优化了你的if-else代码块。