设计模式-策略模式精讲
如下是本文目录:
1. 为什么需要策略模式
1.1 使用策略模式要解决的核心痛点
1.2 正确典型使用场景
1.3 不适合策略模式的场景
2. 五种常见实现方式
2.1 基础接口实现式(原生标准)
2.2 枚举策略式(极简无类爆炸)
2.3 工厂+策略整合式(解耦增强)
2.4 Lambda匿名策略式(轻量化极简)
2.5 Spring容器策略式(工程实战主流)
2.6 五种实现方式对比表
2.7 高级拓展:组合策略(多策略叠加执行)
2.8 拓展案例:文件加密策略
2.9 策略模式UML结构图
2.10 JDK标准库原生策略模式落地(面试高频)
2.11 相似设计模式核心区分表
3. 策略模式核心痛点与解决办法
3.1 策略类过多(类爆炸)
3.2 策略分发硬编码
3.3 空策略、未知策略容错
3.4 策略重复创建、性能冗余
4. 总结
1. 为什么需要策略模式
策略模式是行为型设计模式,核心目标:定义一系列独立的算法(业务策略),将每个算法封装为独立类,让算法可以互相替换,且算法的变化不会影响调用算法的客户端,完美实现业务算法与业务调用逻辑的解耦。
换句话讲:策略模式就是把多变的业务算法单独封装成一个个可替换的独立类,外部调用代码只负责调度,不需要关心算法内部细节,换算法不用改主流程,实现算法和业务代码解绑。
1.1 使用策略模式要解决的核心痛点
a. 业务中大量分支判断(if-else/switch),随着策略增多,代码臃肿冗长、维护成本极高;
b. 算法逻辑高度耦合在业务主类中,修改、新增算法需要改动核心业务代码,违反开闭原则;
c. 不同业务算法逻辑混杂,代码可读性差,容易出现分支逻辑冲突、修改牵一发而动全身的问题;
d. 算法无法独立复用、单独测试,多场景复用相同算法需要重复编码。
1.2 正确典型使用场景
a.多条件分支业务计算:订单支付方式(微信/支付宝/银行卡)、折扣计算(满减/折扣/优惠券)、运费计算;
b.多类型数据处理策略:文件解析(Excel/Word/PDF)、消息推送(短信/邮件/APP推送)、日志输出(控制台/文件/数据库);
c.业务规则动态切换:会员等级权益计算、风控校验规则、排序筛选策略、汇率换算策略;
d.框架层级算法替换:线程池拒绝策略、Spring资源加载策略、序列化策略、缓存淘汰策略。
1.3 不适合策略模式的场景
a.策略极少变动、分支固定:仅1-2个固定分支,业务终身不会新增策略,使用if-else更简洁;
b.算法逻辑极其简单:单句代码即可实现的分支逻辑,拆分策略类会造成类爆炸、过度设计;
c.策略之间存在强依赖、无法独立拆分:多个算法逻辑耦合绑定,拆分后会增加代码复杂度;
d.临时一次性业务策略:无需复用、无需扩展的临时分支逻辑,无需封装策略。
2. 五种常见实现方式
策略模式三大标准核心角色
a.Strategy(策略接口)定义所有算法统一公共规范,对外暴露统一执行方法;
b.ConcreteStrategy(具体策略实现类)独立实现接口,封装单一算法逻辑,单一职责;
c.Context(上下文调度类)持有策略对象引用,提供动态修改策略入口,接收客户端请求并委托策略执行。
2.1 基础接口实现式(原生标准)
特点:定义统一策略接口,不同策略实现接口,上下文类持有策略对象,对外提供统一调用入口,是最经典、最标准的策略模式实现。
// 1. 统一策略接口(定义算法规范) public interface DiscountStrategy { // 计算折扣后价格 BigDecimal calculatePrice(BigDecimal originalPrice); } // 2. 具体策略实现1:无优惠 public class NoDiscountStrategy implements DiscountStrategy { @Override public BigDecimal calculatePrice(BigDecimal originalPrice) { return originalPrice; } } // 3. 具体策略实现2:八折优惠 public class EightDiscountStrategy implements DiscountStrategy { @Override public BigDecimal calculatePrice(BigDecimal originalPrice) { return originalPrice.multiply(new BigDecimal("0.8")); } } // 4. 具体策略实现3:满减优惠 public class FullReductionStrategy implements DiscountStrategy { @Override public BigDecimal calculatePrice(BigDecimal originalPrice) { // 满100减20 return originalPrice.compareTo(new BigDecimal("100")) >= 0 ? originalPrice.subtract(new BigDecimal("20")) : originalPrice; } } // 5. 上下文类(统一调度入口,持有策略) public class DiscountContext { // 持有策略接口引用,运行时通过传入不同实现类来切换优惠算法 (这也是JAVA中多态的核心运用) private DiscountStrategy discountStrategy; // 注入策略 public DiscountContext(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } // 动态修改策略 public void setDiscountStrategy(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } // 对外统一调用方法 public BigDecimal executeCalculate(BigDecimal originalPrice) { return discountStrategy.calculatePrice(originalPrice); } } // 测试使用 public class StrategyTest { public static void main(String[] args) { DiscountContext context = new DiscountContext(new EightDiscountStrategy()); System.out.println("八折后价格:" + context.executeCalculate(new BigDecimal("200"))); // 动态切换策略 context.setDiscountStrategy(new FullReductionStrategy()); System.out.println("满减后价格:" + context.executeCalculate(new BigDecimal("200"))); } }适用场景:策略数量适中、需要动态切换策略、常规业务解耦场景,是企业开发通用首选。
优缺点:结构清晰、完全符合开闭原则、策略可独立扩展;缺点是策略较多时会产生大量独立策略类。
2.2 枚举策略式(极简无类爆炸)
特点:将所有策略封装在枚举内部,通过枚举常量区分不同策略,整合策略定义、实现、分发,无需创建大量独立实现类,代码极简。
// 枚举策略:整合所有折扣算法 public enum DiscountEnumStrategy { // 枚举常量:对应不同策略 NO_DISCOUNT(1, "无优惠") { @Override public BigDecimal calculatePrice(BigDecimal originalPrice) { return originalPrice; } }, EIGHT_DISCOUNT(2, "八折优惠") { @Override public BigDecimal calculatePrice(BigDecimal originalPrice) { return originalPrice.multiply(new BigDecimal("0.8")); } }, FULL_REDUCTION(3, "满减优惠") { @Override public BigDecimal calculatePrice(BigDecimal originalPrice) { return originalPrice.compareTo(new BigDecimal("100")) >= 0 ? originalPrice.subtract(new BigDecimal("20")) : originalPrice; } }; // 策略编码、描述 private final int code; private final String desc; DiscountEnumStrategy(int code, String desc) { this.code = code; this.desc = desc; } // 抽象策略方法,由每个枚举常量实现 public abstract BigDecimal calculatePrice(BigDecimal originalPrice); // 根据code获取对应策略 public static DiscountEnumStrategy getStrategyByCode(int code) { for (DiscountEnumStrategy strategy : values()) { if (strategy.code == code) { return strategy; } } throw new IllegalArgumentException("无效的优惠策略编码"); } // getter方法 public int getCode() { return code; } public String getDesc() { return desc; } } // 测试使用 public class EnumStrategyTest { public static void main(String[] args) { // 根据编码获取策略并执行 DiscountEnumStrategy strategy = DiscountEnumStrategy.getStrategyByCode(2); System.out.println(strategy.getDesc() + "价格:" + strategy.calculatePrice(new BigDecimal("200"))); } }适用场景:策略固定、不会频繁新增,策略数量可控的业务场景,替代大量简单策略类。
优缺点:杜绝类爆炸、代码集中统一管理、调用简洁;缺点是策略逻辑复杂时,枚举类会过于臃肿,不适合复杂算法。
2.3 工厂+策略整合式(解耦增强)
特点:结合简单工厂模式,新增策略工厂类统一创建、分发策略对象,彻底屏蔽策略创建细节,客户端无需感知具体策略实现类,进一步解耦。
// 1. 策略接口(同基础实现) public interface DiscountStrategy { BigDecimal calculatePrice(BigDecimal originalPrice); } // 2. 具体策略实现 public class NoDiscountStrategy implements DiscountStrategy { @Override public BigDecimal calculatePrice(BigDecimal originalPrice) { return originalPrice; } } public class EightDiscountStrategy implements DiscountStrategy { @Override public BigDecimal calculatePrice(BigDecimal originalPrice) { return originalPrice.multiply(new BigDecimal("0.8")); } } // 3. 策略工厂类:统一生产策略 public class StrategyFactory { // 缓存所有策略,避免重复创建 private static final Map<String, DiscountStrategy> STRATEGY_CACHE = new HashMap<>(); // 静态初始化所有策略 static { STRATEGY_CACHE.put("noDiscount", new NoDiscountStrategy()); STRATEGY_CACHE.put("eightDiscount", new EightDiscountStrategy()); } // 根据key获取策略 public static DiscountStrategy getStrategy(String strategyKey) { DiscountStrategy strategy = STRATEGY_CACHE.get(strategyKey); if (strategy == null) { throw new IllegalArgumentException("不存在该优惠策略"); } return strategy; } } // 4. 上下文调用 public class FactoryStrategyContext { public BigDecimal executePrice(String strategyKey, BigDecimal originalPrice) { // 工厂获取策略,直接执行 DiscountStrategy strategy = StrategyFactory.getStrategy(strategyKey); return strategy.calculatePrice(originalPrice); } } // 测试使用 public class FactoryStrategyTest { public static void main(String[] args) { FactoryStrategyContext context = new FactoryStrategyContext(); BigDecimal result = context.executePrice("eightDiscount", new BigDecimal("200")); System.out.println("优惠后价格:" + result); } }适用场景:策略数量多、需要统一管理策略实例、客户端无需感知策略细节的中大型业务场景。
优缺点:职责拆分更清晰、屏蔽创建细节、支持策略缓存复用;缺点是多了工厂类,小幅增加代码量。
2.4 Lambda匿名策略式(轻量化极简)
特点:基于Java8函数式接口特性,无需定义策略实现类,通过Lambda表达式动态实现策略逻辑,适合简单、临时性策略。
// 1. 函数式策略接口(必须仅有一个抽象方法) @FunctionalInterface public interface DiscountStrategy { BigDecimal calculatePrice(BigDecimal originalPrice); } // 2. 上下文类 public class LambdaStrategyContext { private DiscountStrategy discountStrategy; public void setDiscountStrategy(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public BigDecimal executeCalculate(BigDecimal originalPrice) { return discountStrategy.calculatePrice(originalPrice); } } // 测试使用:Lambda动态传入策略,无需定义实现类 public class LambdaStrategyTest { public static void main(String[] args) { LambdaStrategyContext context = new LambdaStrategyContext(); // 无优惠策略 context.setDiscountStrategy(price -> price); System.out.println("无优惠价格:" + context.executeCalculate(new BigDecimal("200"))); // 八折策略 context.setDiscountStrategy(price -> price.multiply(new BigDecimal("0.8"))); System.out.println("八折价格:" + context.executeCalculate(new BigDecimal("200"))); // 满减策略 context.setDiscountStrategy(price -> price.compareTo(new BigDecimal("100")) >= 0 ? price.subtract(new BigDecimal("20")) : price); System.out.println("满减价格:" + context.executeCalculate(new BigDecimal("200"))); } }适用场景:策略逻辑简单、临时动态定义、无需复用策略的场景,快速简化分支代码。
优缺点:极致精简、无需创建大量实现类、灵活度高;缺点是复杂策略逻辑会导致代码臃肿,无法单独复用和单元测试。
2.5 Spring容器策略式(工程实战主流)
特点:基于Spring IoC容器,将所有策略实现类交给Spring管理,通过依赖注入、Map自动注入批量获取所有策略,实现自动分发,是实际项目中最常用的实现方式。
// 1. 策略接口 public interface PayStrategy { // 支付方法 String pay(BigDecimal money); } // 2. 具体策略实现(交给Spring管理) @Component("wechatPay") public class WechatPayStrategy implements PayStrategy { @Override public String pay(BigDecimal money) { return "微信支付成功,支付金额:" + money; } } @Component("aliPay") public class AliPayStrategy implements PayStrategy { @Override public String pay(BigDecimal money) { return "支付宝支付成功,支付金额:" + money; } } @Component("bankPay") public class BankPayStrategy implements PayStrategy { @Override public String pay(BigDecimal money) { return "银行卡支付成功,支付金额:" + money; } } // 3. 策略上下文/调度器:Spring自动注入所有策略 @Component public class PayStrategyContext { // 自动注入所有PayStrategy实现类,key为组件名称,value为策略实例 @Autowired private Map<String, PayStrategy> payStrategyMap; // 根据支付类型获取策略并执行 public String executePay(String payType, BigDecimal money) { PayStrategy strategy = payStrategyMap.get(payType); if (strategy == null) { throw new IllegalArgumentException("不支持的支付方式"); } return strategy.pay(money); } } // 测试(Spring环境) @SpringBootTest public class SpringStrategyTest { @Autowired private PayStrategyContext payStrategyContext; @Test public void testPay() { System.out.println(payStrategyContext.executePay("wechatPay", new BigDecimal("99.9"))); System.out.println(payStrategyContext.executePay("aliPay", new BigDecimal("199.9"))); } }适用场景:SpringBoot/Spring工程、企业级业务开发、需要频繁扩展策略、需要统一管理策略实例的场景。
优缺点:无需手动创建管理策略、新增策略只需新增实现类即可(完全开闭)、适配业务迭代;缺点是依赖Spring容器,非原生Java实现。
2.6 五种实现方式对比表
实现方式 | 开闭扩展性 | 是否类爆炸 | 代码简洁度 | 依赖环境 | 适用场景 |
|---|---|---|---|---|---|
基础接口实现式 | 优秀 | 是 | 中等 | 原生Java | 通用业务、需要动态切换策略 |
枚举策略式 | 一般 | 否 | 高 | 原生Java | 策略固定、数量少、无需频繁扩展 |
工厂+策略整合式 | 优秀 | 是 | 中等 | 原生Java | 多策略统一管理、屏蔽创建细节 |
Lambda匿名策略式 | 差 | 否 | 极高 | JDK8+ | 简单临时策略、快速简化分支代码 |
Spring容器策略式 | 极佳 | 可控 | 高 | Spring环境 | 企业级项目、长期迭代扩展业务 |
注:类爆炸问题可通过分包管理(strategy/impl)优化;Spring策略式虽有多个实现类,但符合业务分层规范,是工程中最优解;简单场景无需过度封装,优先选择轻量化实现。
2.7 高级拓展:组合策略(多策略叠加执行)
业务中常需要多个策略叠加生效(满减后再打折),通过组合类统一串联执行所有策略:
// 策略组合器 public class CombinedDiscountStrategy implements DiscountStrategy { private final List<DiscountStrategy> strategies; public CombinedDiscountStrategy(DiscountStrategy... strategies) { this.strategies = Arrays.asList(strategies); } @Override public double applyDiscount(double originalPrice) { double currentPrice = originalPrice; //模拟写法,生产上肯定会有更复杂的组合关系 for (DiscountStrategy strategy : strategies) { currentPrice = strategy.applyDiscount(currentPrice); } return currentPrice; } } // 使用:满300减50,再9折 DiscountStrategy combined = new CombinedDiscountStrategy( new FullReductionStrategy(300, 50), new PercentageDiscountStrategy(0.1) );2.8 拓展案例:文件加密策略
// 策略接口 public interface EncryptionStrategy { String encrypt(String content); String decrypt(String encryptedContent); } // AES加密策略 public class AesEncryptionStrategy implements EncryptionStrategy {} // RSA加密策略 public class RsaEncryptionStrategy implements EncryptionStrategy {} // 上下文 public class FileEncryptor { private EncryptionStrategy strategy; public FileEncryptor(EncryptionStrategy strategy) { this.strategy = strategy; } public void encryptFile(String inputPath, String outputPath) { String content = readFile(inputPath); String encrypted = strategy.encrypt(content); writeFile(outputPath, encrypted); } }2.9 策略模式UML结构图
_________________________ | Strategy | |------------------------| | + executeAlgorithm() | |________________________| ▲ ____________|_____________ | | | ______▼______ ____▼______ _____▼______ | Concrete | | Concrete | | Concrete | | StrategyA | | StrategyB | | StrategyC | |___________| |___________| |___________| ▲ | _____▼_____ | Context | |-----------| | - strategy| |___________|2.10 JDK标准库原生策略模式落地(面试高频)
Comparator比较器:
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5); Collections.sort(numbers, Comparator.naturalOrder()); // 升序策略 Collections.sort(numbers, Comparator.reverseOrder()); // 降序策略ThreadPoolExecutor线程池拒绝策略:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10), new ThreadPoolExecutor.AbortPolicy() // 可切换Discard/CallerRuns等策略 );2.11 相似设计模式核心区分表
| 模式 | 核心区别 |
|---|---|
| 工厂模式 | 关注对象创建;策略模式关注算法行为选择与替换 |
| 命令模式 | 封装单次操作请求,支持撤销、排队;策略模式封装可复用完整算法 |
| 状态模式 | 内部状态自动流转切换;策略模式需要客户端显式指定切换算法 |
3. 策略模式核心痛点与解决办法
3.1 策略类过多(类爆炸)
问题描述:业务策略繁多时,基础实现式会产生大量策略实现类,项目结构臃肿,不利于管理。
解决方案:
1.简单固定策略:使用枚举策略式统一收拢所有策略,无需拆分独立类;
2.简单动态策略:使用Lambda表达式动态定义,省去实现类;
3.复杂多策略:通过分包管理,将策略实现类统一放入strategy/impl包,规范项目结构。
3.2 策略分发硬编码
问题描述:传统写法中,客户端通过硬编码key、分支判断获取策略,新增策略需要修改分发代码,违反开闭原则。
解决方案:
1.原生场景:使用工厂模式+缓存Map统一分发,无需修改核心代码;
2.Spring场景:利用Spring自动注入Map,新增策略只需新增实现类,自动注册生效;
3.统一策略编码规范:通过配置文件、枚举映射策略key,彻底消除硬编码。
3.3 空策略、未知策略容错
问题描述:传入不存在的策略key,会出现空指针、未知异常,程序健壮性差。
解决方案:
1. 策略获取时增加非空判断,兜底返回默认策略;
2.自定义业务异常,替换原生空指针异常,统一异常提示;
3. 枚举策略通过code遍历匹配,无匹配时抛出明确异常。
3.4 策略重复创建、性能冗余
问题描述:每次调用策略都新建对象,频繁调用会产生大量临时对象,造成GC压力。
解决方案:
1. 原生场景:通过静态Map缓存所有策略实例,全局复用;
2. Spring场景:利用Spring单例特性,策略类默认单例托管,无需手动缓存;
3. 无状态策略天然可复用,禁止每次调用new新对象。
4. 总结
策略模式的核心是解耦分支算法、拥抱开闭原则、实现算法可替换,规避臃肿的if-else代码,选择实现方式需结合业务场景:
1.企业Spring项目首选Spring容器策略式:扩展性最强、适配业务迭代、无需手动管理实例;
2.固定少量策略选枚举式:代码简洁、无类爆炸、统一管理;
3.简单临时策略选Lambda式:极致精简,快速优化分支代码;
4.原生Java通用场景选基础/工厂策略式:结构标准、通用性强;
5.坚决避免过度设计:少量固定分支、无扩展需求的场景,无需强行使用策略模式。
理解策略模式的核心思想而非死记代码,才能在复杂业务中彻底消灭分支嵌套,写出高扩展、高维护、高复用的业务代码。
