1. 策略模式到底解决了什么问题第一次接触策略模式是在做一个电商促销模块的时候。当时产品提了一个需求商品要支持多种折扣方式包括满减、打折、会员价、限时秒杀价而且后续还会不断增加新的促销类型。我一开始的做法很简单写了一个calculatePrice方法里面用if-else一路判断下去。结果不到两周这个方法膨胀到了三百多行每次加一种促销就要改动这个方法改完之后还要把所有历史促销回归测试一遍生怕碰坏了别的逻辑。这个场景就是策略模式最典型的用武之地。策略模式的核心思想用一句话概括把一组可互换的算法分别封装起来让它们可以互相替换且算法的变化独立于使用它的客户端。翻译成大白话就是你有一堆做同一件事但做法不同的方案把它们各自独立打包用的时候挑一个就行不用在一堆if-else里翻来翻去。它解决的问题本质上是消除大量的条件分支同时满足开闭原则——对扩展开放对修改关闭。新增一种策略时你只需要新增一个类不需要动已有的代码。这在多人协作的项目里尤其重要因为不动老代码就意味着不会引入回归缺陷。策略模式适合谁学我的判断是写过超过两百行if-else嵌套的人、维护过“牵一发动全身”的巨型方法的人、准备面试被问到设计模式的人以及正在做课程大作业需要体现设计能力的同学。它属于行为型模式在 23 种设计模式里策略模式是日常业务开发中出现频率最高的几个之一几乎每个 Java 后端项目都能找到它的影子。2. 策略模式的结构拆解与角色分工2.1 三个核心角色策略模式的结构非常干净只有三个角色但每个角色的职责边界必须划清楚否则写着写着就退化成“换汤不换药”的伪策略模式。抽象策略Strategy通常是一个接口或者抽象类定义了所有策略必须实现的算法方法签名。它是客户端和具体策略之间的契约。比如定义一个DiscountStrategy接口里面有个calculate(double originalPrice)方法。具体策略ConcreteStrategy实现了抽象策略接口的各个类每个类封装一种具体的算法实现。比如FullReductionStrategy、PercentageDiscountStrategy、MemberPriceStrategy各管各的。上下文Context持有一个抽象策略的引用负责在运行时接收客户端传入的具体策略并把实际的算法调用委托给这个策略对象。上下文本身不关心算法怎么实现它只负责调度。我见过很多初学者把上下文写成了一个“大管家”在里面又加了一堆判断逻辑这就完全跑偏了。上下文的职责应该很纯粹持有策略、委托调用。一旦你在上下文里又开始写if (type ...)说明抽象没做干净。2.2 为什么不用继承而用组合这是理解策略模式的关键。假设我们用继承来实现不同的折扣BasePrice作为父类FullReductionPrice、PercentagePrice继承它各自重写calculate方法。看起来也能用但问题在于继承是编译期绑定的组合是运行期绑定的。用继承的话一个订单对象在创建时它的价格计算方式就固定了你没法在运行时根据用户等级、活动状态动态切换。而策略模式通过组合把策略对象作为上下文的一个字段可以在任何时候setStrategy(new XxxStrategy())来切换。这就是“运行时动态替换”的能力也是策略模式最核心的价值。另一个原因是继承层次会爆炸。如果折扣方式和用户等级两个维度都用继承你就需要FullReductionVipPrice、FullReductionNormalPrice、PercentageVipPrice……类数量呈乘法增长。而策略模式配合组合可以灵活拼装。2.3 和工厂模式的区别与配合经常有人把策略模式和工厂模式搞混。简单区分工厂模式关注的是“怎么创建对象”策略模式关注的是“怎么选择行为”。工厂负责把对象造出来策略负责把行为选出来。在实际项目里这两个模式经常搭配使用。用一个简单工厂或者 Map 来管理所有策略实例根据传入的类型标识从工厂里取出对应的策略再交给上下文执行。这样客户端连new具体策略都不需要进一步降低耦合。后面实操部分我会给出这个组合的完整代码。3. 从零实现一个可落地的策略模式3.1 场景定义与接口设计我以一个订单折扣系统为例完整走一遍实现过程。需求是这样的订单支持四种折扣——无折扣、满 300 减 50、打 8.5 折、会员价 9 折。后续可能增加“第二件半价”等新玩法。先定义抽象策略接口public interface DiscountStrategy { /** * 计算折扣后的价格 * param originalPrice 原价 * return 折后价格 */ double calculate(double originalPrice); /** * 策略类型标识用于工厂匹配 */ String getType(); }这里我特意加了getType()方法。很多教程里不写这个方法结果到了工厂匹配那一步就得用instanceof或者反射非常别扭。让每个策略自己声明类型标识工厂用 Map 注册是最干净的做法。接口设计有个经验方法参数尽量用基本类型或简单的值对象不要传整个 Order 对象进来。我踩过这个坑早期接口签名是calculate(Order order)结果每个策略实现里都要去 order 里取各种字段策略和 Order 的字段耦合死了。后来改成只传必要的数值策略变成了纯粹的计算单元复用性大幅提升。3.2 四种具体策略的实现public class NoDiscountStrategy implements DiscountStrategy { Override public double calculate(double originalPrice) { return originalPrice; } Override public String getType() { return NONE; } } public class FullReductionStrategy implements DiscountStrategy { private static final double THRESHOLD 300.0; private static final double REDUCTION 50.0; Override public double calculate(double originalPrice) { if (originalPrice THRESHOLD) { return originalPrice - REDUCTION; } return originalPrice; } Override public String getType() { return FULL_REDUCTION; } } public class PercentageDiscountStrategy implements DiscountStrategy { private final double rate; public PercentageDiscountStrategy(double rate) { this.rate rate; } Override public double calculate(double originalPrice) { return originalPrice * rate; } Override public String getType() { return PERCENTAGE; } } public class MemberPriceStrategy implements DiscountStrategy { private static final double MEMBER_RATE 0.9; Override public double calculate(double originalPrice) { return originalPrice * MEMBER_RATE; } Override public String getType() { return MEMBER; } }注意PercentageDiscountStrategy我把折扣率做成了构造参数而不是写死。这样同一个类可以实例化成 8.5 折、7 折、6 折等多个对象避免了为每个折扣率都建一个类。这是策略模式的一个实用技巧把变化的部分参数化而不是类化。3.3 上下文与策略工厂的配合上下文类public class PriceContext { private DiscountStrategy strategy; public PriceContext() { this.strategy new NoDiscountStrategy(); } public void setStrategy(DiscountStrategy strategy) { if (strategy null) { throw new IllegalArgumentException(策略不能为空); } this.strategy strategy; } public double execute(double originalPrice) { return strategy.calculate(originalPrice); } }策略工厂public class StrategyFactory { private static final MapString, DiscountStrategy STRATEGY_MAP new HashMap(); static { STRATEGY_MAP.put(NONE, new NoDiscountStrategy()); STRATEGY_MAP.put(FULL_REDUCTION, new FullReductionStrategy()); STRATEGY_MAP.put(PERCENTAGE, new PercentageDiscountStrategy(0.85)); STRATEGY_MAP.put(MEMBER, new MemberPriceStrategy()); } public static DiscountStrategy getStrategy(String type) { DiscountStrategy strategy STRATEGY_MAP.get(type); if (strategy null) { throw new IllegalArgumentException(未知的策略类型: type); } return strategy; } public static void register(String type, DiscountStrategy strategy) { STRATEGY_MAP.put(type, strategy); } }工厂里我留了一个register方法这是为扩展准备的。如果策略需要从数据库或配置中心动态加载可以在启动时调用register注册进去而不需要改工厂的静态代码块。客户端调用就变得非常清爽public class OrderService { public double calculateOrderPrice(double originalPrice, String strategyType) { PriceContext context new PriceContext(); context.setStrategy(StrategyFactory.getStrategy(strategyType)); return context.execute(originalPrice); } }对比一下最初那个三百行的if-else现在新增一种折扣只需要写一个实现类在工厂里注册一行。老代码一行不动。这就是策略模式带来的实际收益。3.4 用枚举优化策略管理如果你的策略种类是固定的、有限的用枚举来管理会更优雅。枚举天然是单例还能把类型标识和实现绑定在一起public enum DiscountStrategyEnum { NONE(NONE) { Override public double calculate(double price) { return price; } }, FULL_REDUCTION(FULL_REDUCTION) { Override public double calculate(double price) { return price 300 ? price - 50 : price; } }; private final String type; DiscountStrategyEnum(String type) { this.type type; } public abstract double calculate(double price); public String getType() { return type; } public static DiscountStrategyEnum of(String type) { for (DiscountStrategyEnum e : values()) { if (e.type.equals(type)) { return e; } } throw new IllegalArgumentException(未知策略: type); } }枚举方式的好处是代码集中、不需要额外的工厂类、线程安全天然保证。缺点是当策略逻辑复杂、依赖外部服务时枚举里写不下那么多代码还是得用接口方式。我的选择标准是策略逻辑在 20 行以内且无外部依赖用枚举否则用接口加工厂。4. 策略模式在真实项目中的典型应用场景4.1 支付方式路由这是策略模式最经典的应用。一个电商系统支持多种支付渠道每种渠道的下单、查询、退款逻辑都不同。用策略模式把每种支付渠道封装成一个策略客户端根据用户选择的支付方式路由到对应策略。新增支付渠道时只加一个策略类不动主流程。这里有个实操细节支付策略往往需要依赖不同的第三方 SDK构造比较重。这时候策略工厂应该做成单例缓存而不是每次调用都new一个策略对象。我一般用 Spring 的话直接把所有策略实现类交给容器管理通过MapString, PayStrategy注入Spring 会自动把 bean 名称作为 key 组装成 Map非常方便。4.2 多种数据导出格式后台管理系统经常需要导出 Excel、CSV、PDF 三种格式。三种格式的生成逻辑差异很大但对外接口都是“给一批数据返回一个文件流”。这正是策略模式的形状。定义一个ExportStrategy接口三个实现类各管一种格式前端传格式参数后端路由到对应策略。我在这个场景踩过的坑是导出策略里往往有大量重复的“数据预处理”逻辑比如字段脱敏、日期格式化。这些公共逻辑不应该在每个策略里复制一遍。我的做法是在上下文里做预处理把处理好的数据传给策略策略只负责“怎么渲染成目标格式”。公共逻辑上提到上下文差异化逻辑下沉到策略这个分层原则能避免大量重复代码。4.3 风控规则引擎风控场景里同一条交易请求可能要经过多种规则的校验黑名单校验、金额阈值校验、频次校验、地域校验。每种校验的算法不同但都是“输入交易输出通过或拒绝”。用策略模式把每种规则封装成策略再配合责任链模式串起来就是一套可配置的规则引擎。这个场景的注意点是策略之间可能有执行顺序要求而且某些策略失败后要短路后续策略。这时候单纯用策略模式不够需要策略模式加责任链模式组合。策略负责“怎么校验”责任链负责“按什么顺序校验、失败后怎么办”。两个模式各司其职不要试图用一个模式解决所有问题。4.4 计费与结算系统SaaS 产品的计费模块通常支持多种计费方式按量计费、包月、包年、阶梯计价。每种计费方式的算法完全不同但对外都是“给定用量算出费用”。策略模式在这里能很好地隔离变化。阶梯计价这种策略还可以进一步参数化把阶梯配置作为构造参数传入一个类支持多种阶梯方案。5. 常见问题与避坑指南5.1 策略类数量爆炸怎么办这是策略模式被吐槽最多的问题。一个系统如果有几十种策略就会有几十个类文件包结构看起来很臃肿。我的应对策略有三条第一能参数化的不类化。前面提到的折扣率就是典型例子一个PercentageDiscountStrategy通过构造参数支持任意折扣率而不是每个折扣率建一个类。第二用枚举或 Lambda 简化简单策略。Java 8 之后如果策略接口是函数式接口只有一个抽象方法可以直接用 Lambda 表达式代替匿名类代码量大幅减少。比如MapString, DiscountStrategy map new HashMap(); map.put(NONE, price - price); map.put(MEMBER, price - price * 0.9);第三按业务域分包。不要把所有策略堆在一个strategy包里而是按业务域分到order.strategy、payment.strategy、export.strategy各自包里结构清晰也符合模块化思想。5.2 策略之间需要共享状态怎么办有时候多个策略需要访问同一份上下文数据比如用户信息、订单信息。如果每个策略都自己去查一遍既浪费又容易不一致。我的做法是定义一个StrategyContext值对象把策略需要的所有数据一次性组装好作为参数传给策略方法。但要注意这个值对象应该是不可变的。我见过有人在策略里修改了传入的上下文对象导致后续策略拿到脏数据。策略应该是无副作用的纯计算单元只读不写。如果确实需要写回结果通过返回值传递而不是修改入参。5.3 运行时切换策略的线程安全问题如果上下文对象是多线程共享的setStrategy和execute之间就存在竞态条件。线程 A 刚设置完策略线程 B 就把策略换掉了A 执行时用的是 B 的策略。这个 bug 非常隐蔽因为大多数时候不会复现。解决方案有两种一是每次请求创建新的上下文对象用完即弃这是最推荐的做法上下文本身很轻量创建开销可以忽略二是如果上下文必须共享把策略作为方法参数传入而不是作为字段存储这样就不存在共享状态了。我倾向于第一种简单直接。5.4 策略选择逻辑该放在哪里策略模式只解决了“策略怎么封装和替换”但“根据什么条件选择哪个策略”这个逻辑它不管。很多初学者把选择逻辑写在了客户端结果客户端又变成了一堆if-else等于白折腾。正确的做法是把选择逻辑也封装起来通常放在工厂里。工厂根据传入的类型标识返回策略客户端只负责传标识。如果选择逻辑很复杂比如要根据多个条件组合判断可以单独抽一个StrategySelector类专门负责决策。这样客户端、选择器、策略三者职责分明。5.5 常见问题速查表问题现象根本原因解决思路策略类越来越多包很乱未做参数化简单差异也建类参数化构造、Lambda、按域分包客户端仍有大量 if-else选择逻辑未封装引入工厂或选择器类多线程下策略错乱上下文共享且可变每请求新建上下文或策略作参数策略间重复代码多公共逻辑未上提公共逻辑放上下文差异下沉策略新增策略要改工厂代码工厂硬编码提供 register 方法或 Spring 自动注入策略依赖外部服务难测试策略职责过重依赖注入策略只做纯计算6. 面试与考试中的策略模式速记6.1 一句话记忆口诀策略模式的口诀我总结为“一接口多实现上下文里持有它运行时来替换它”。这四句对应了策略模式的四个关键点抽象策略是接口、具体策略是多实现、上下文持有引用、运行时可替换。和它容易混淆的是状态模式。两者的类结构几乎一样区别在于意图策略模式是客户端主动选择算法策略之间是平等的、可互换的状态模式是对象内部状态变化导致行为变化状态之间有流转关系。考试里如果题目强调“根据用户选择”“可配置”“多种方案”选策略如果强调“状态流转”“自动切换”“生命周期”选状态。6.2 面试高频追问面试官问策略模式通常不会只让你背定义而是会追问几个点。第一个常问的是“策略模式和简单工厂的区别”答案前面说过一个管行为选择一个管对象创建。第二个常问的是“策略模式如何满足开闭原则”答新增策略只需新增类不改已有代码。第三个常问的是“策略模式的缺点”答类数量增加、客户端必须知道有哪些策略、策略选择逻辑需要额外封装。还有一个进阶追问是“Spring 里怎么优雅地实现策略模式”。标准答案是定义策略接口所有实现类加Component构造注入MapString, StrategySpring 会把 bean 名称作为 key 自动组装。如果 bean 名称不符合业务标识可以用自定义注解标注类型启动时扫描注册。这个回答能体现你真正在项目里用过而不是只背过书。6.3 大作业与课程设计的加分点如果是做课程大作业光实现基本策略模式只能拿及格分。想拿高分建议加上这几点用 UML 类图清晰画出三个角色的关系结合工厂模式做策略管理用配置文件或注解实现策略的动态注册写单元测试覆盖每个策略在文档里说明为什么这里用策略模式而不是继承或 if-else。这几点加上去设计模式的“设计”二字才体现得出来。7. 我个人的几点实操体会策略模式我用得比较多踩的坑也不少分享几个文档里不会写的经验。第一个体会是不要为了用而用。如果一个场景只有两种策略而且未来大概率不会增加那老老实实写if-else反而更清晰。策略模式的价值在于“多种且会增长”如果策略数量固定且很少引入策略模式是过度设计。我见过有人把只有两个分支的逻辑硬拆成策略模式结果代码量翻了三倍维护的人骂娘。第二个体会是策略的粒度要拿捏好。太粗一个策略里又塞了一堆if-else等于没拆太细每个策略只做一行计算类数量爆炸。我的经验是一个策略应该对应一个完整的、有业务含义的算法单元比如“满减”“阶梯计价”这种而不是“判断是否满 300”“减去 50”这种原子操作。第三个体会是策略的命名要见名知意。我见过StrategyA、StrategyB这种命名过两个月自己都不知道哪个是哪个。命名应该体现业务含义比如FullReductionDiscountStrategy、TieredPricingStrategy。类名长一点没关系可读性比省那几个字符重要得多。第四个体会是配合配置中心使用效果更好。如果策略的启用与否、参数配置能放到配置中心那么运营人员改个配置就能调整策略不需要发版。比如折扣率从 0.85 改成 0.8改配置即可生效。这时候策略类只负责算法骨架具体参数从配置读取灵活性和可维护性都上了一个台阶。最后分享一个我常用的扩展思路策略模式加模板方法模式。如果多个策略有相同的执行骨架比如都要先校验参数、再计算、最后格式化结果可以把骨架提到抽象策略类里用模板方法定义具体策略只实现差异化的计算步骤。这样既保留了策略的可替换性又消除了策略间的重复代码。这个组合在计费、风控这类场景里特别好用。
企业数字化 ERP 产品动态
相关推荐
鱼香鸡蛋源码解析:从语法到项目的3个关键步骤 鱼香鸡蛋源码解析:从语法到项目的3个关键步骤 学会语法却不知怎么搭项目,这是多数开发者卡在初级阶段的死结。你背熟了 for 循环和 if 判断,打开 IDE 却对着空白文件发呆。别急, 源码解析… · 2026/9/23 13:20:34
Android加密从Blowfish迁移到AES-GCM:安全选型与实战改造指南 接手过一个老项目,里面对用户手机号做加密存储用的就是Blowfish,当时第一反应是“这玩意儿还活着呢?”查了一圈资料发现,Blowfish确实是加密算法界的“老前辈”,1993年由Bruce Schneier设计的对称分组密码,… · 2026/9/23 13:20:34
从ff.c到invalid signature:嵌入式文件系统与固件签名全解析 前两天在 Gitee 上翻一个嵌入式开源项目,仓库地址是 wuming/fatfs,点进去看的是 ff.c 这个文件,浏览器标题栏里挂着一串 signature8078a4ab63c88f9c690526bc05ffbacc。这串字符第一次看像是随机乱码,实际上它是 Git 体系里的提交哈… · 2026/9/23 13:20:33
视频压缩编码保姆级教程:搞定这5个高频面试题 视频压缩编码保姆级教程:搞定这5个高频面试题 配环境卡了三天?FFmpeg 装不上,libx264 编译报错,Python 库版本冲突。这种崩溃感我太懂了。… · 2026/9/23 14:07:24
大语言模型技术发展与应用场景探索研究 刚接触一个新领域,最怕的就是迷失在海量的外国文献里,读了很多篇还是理不清脉络。我曾经也以为“研究现状”只能靠逐篇阅读、手动总结,直到发现了一些能生成“知识图谱”的神器。它们能让你像开了上帝视角一样,瞬间看清一个领域的… · 2026/9/23 14:07:24
C++ MFC五子棋人机对战:从课程设计到可运行桌面程序 简介:这是一份面向高校C课程学习者与Windows桌面开发入门者的期末大作业参考方案,围绕MFC框架实现人机对战五子棋,帮助读者理解面向对象设计、界面开发与博弈算法的结合方式。压缩包共36个文件,约160KB,以cpp与h源码为… · 2026/9/23 14:07:18
上市公司新闻文本分类:从数据清洗到TF-IDF模型实战 简介:这份源码面向具备一定Python基础的金融数据分析学习者与量化研究者,提供一套完整的上市公司新闻文本分析与分类预测方案,解决财经新闻自动抓取、特征提取与模型分类的实践问题。资源包共21个文件,以17个Python源代码文件为核… · 2026/9/23 14:07:18
爆客商圈源码解析:微信私域运营后台技术实现指南 简介:本资源为基于HTML5技术开发的商业社交类轻应用「爆客商圈」v1.1.24完整源码包,面向前端开发者、H5跨平台项目实践者及中小商家数字化工具学习者,适用于快速搭建本地化商圈服务平台或二次开发定制化营销功能。压缩包共51个文件࿰… · 2026/9/23 14:07:12
奶牛新手避坑指南:版本升级API全变后的生存法则 奶牛新手避坑指南:版本升级API全变后的生存法则 版本升级后 API 全变了,代码跑不通,文档对不上,这才是开发最崩溃的时刻。这份奶牛新手避坑指南,专门拆解升级后的核心陷阱。别急着骂娘,看完这篇,你的报错能少一半。… · 2026/9/23 14:07:05
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29