1. 策略模式入门:为什么我们需要它?
刚入行时我经常遇到这样的场景:一个订单处理系统需要支持多种支付方式(支付宝、微信、银联),每次新增支付方式都要在原有代码里添加if-else分支。直到某天发现支付模块已经变成了2000行的"巨无霸"类,我才意识到需要策略模式来拯救。
策略模式(Strategy Pattern)的本质是定义算法族,分别封装起来,让它们之间可以互相替换。这种模式让算法的变化独立于使用算法的客户端。在实际项目中,它特别适合处理以下场景:
- 一个系统需要动态地在几种算法中选择一种
- 需要避免使用多重条件判断语句
- 希望对外隐藏具体算法实现细节
关键理解:策略模式不是简单的"把if-else拆成多个类",而是通过组合(composition)而非继承(inheritance)来实现行为扩展,这符合"多用组合,少用继承"的设计原则。
2. 策略模式实战:电商促销案例
2.1 基础实现三步走
假设我们要实现一个电商促销系统,支持满减、折扣和赠品三种促销策略。以下是典型实现步骤:
- 定义策略接口
public interface PromotionStrategy { BigDecimal applyPromotion(BigDecimal price); }- 实现具体策略类
// 满减策略 public class FullReductionStrategy implements PromotionStrategy { private final BigDecimal condition; private final BigDecimal reduction; public FullReductionStrategy(BigDecimal condition, BigDecimal reduction) { this.condition = condition; this.reduction = reduction; } @Override public BigDecimal applyPromotion(BigDecimal price) { return price.compareTo(condition) >= 0 ? price.subtract(reduction) : price; } } // 折扣策略 public class DiscountStrategy implements PromotionStrategy { private final BigDecimal discountRate; public DiscountStrategy(BigDecimal discountRate) { this.discountRate = discountRate; } @Override public BigDecimal applyPromotion(BigDecimal price) { return price.multiply(discountRate); } }- 创建策略上下文
public class PromotionContext { private PromotionStrategy strategy; public PromotionContext(PromotionStrategy strategy) { this.strategy = strategy; } public BigDecimal executeStrategy(BigDecimal price) { return strategy.applyPromotion(price); } public void setStrategy(PromotionStrategy strategy) { this.strategy = strategy; } }2.2 实际使用示例
public class Client { public static void main(String[] args) { // 双十一活动:满300减50 PromotionContext context = new PromotionContext( new FullReductionStrategy(new BigDecimal("300"), new BigDecimal("50"))); BigDecimal price = new BigDecimal("350"); System.out.println("优惠后价格:" + context.executeStrategy(price)); // 临时改为8折促销 context.setStrategy(new DiscountStrategy(new BigDecimal("0.8"))); System.out.println("改为折扣后价格:" + context.executeStrategy(price)); } }避坑指南:BigDecimal在金融计算中必须使用String构造器(如new BigDecimal("0.8")),直接使用new BigDecimal(0.8)会出现精度问题。
3. 进阶技巧:策略模式的工程化实践
3.1 策略工厂模式
当策略较多时,可以使用工厂模式管理策略实例:
public class StrategyFactory { private static final Map<String, PromotionStrategy> strategies = new HashMap<>(); static { strategies.put("FULL_REDUCTION", new FullReductionStrategy(new BigDecimal("300"), new BigDecimal("50"))); strategies.put("DISCOUNT", new DiscountStrategy(new BigDecimal("0.8"))); // 可以配置化加载更多策略 } public static PromotionStrategy getStrategy(String type) { PromotionStrategy strategy = strategies.get(type); if(strategy == null) { throw new IllegalArgumentException("未知策略类型:" + type); } return strategy; } }3.2 Spring集成方案
在Spring项目中,可以更优雅地管理策略:
- 使用@Service标注策略实现类
@Service("fullReductionStrategy") public class FullReductionStrategy implements PromotionStrategy {...} @Service("discountStrategy") public class DiscountStrategy implements PromotionStrategy {...}- 自动注入策略集合
@Service public class PromotionService { @Autowired private Map<String, PromotionStrategy> strategyMap; public BigDecimal applyPromotion(String strategyType, BigDecimal price) { PromotionStrategy strategy = strategyMap.get(strategyType); if(strategy == null) { throw new IllegalArgumentException("无效策略类型"); } return strategy.applyPromotion(price); } }性能提示:策略对象通常是无状态的,可以设计为单例复用。但如果有线程安全问题,需要使用ThreadLocal或每次新建实例。
4. 原理深度解析:策略模式与SOLID原则
4.1 单一职责原则(SRP)
每个策略类只负责一个具体的算法实现,比如FullReductionStrategy只处理满减逻辑,DiscountStrategy只处理折扣逻辑。这比把所有算法堆在一个类中更符合SRP。
4.2 开闭原则(OCP)
当需要新增促销策略(比如"第二件半价")时:
- 不需要修改现有策略类
- 只需新增一个SecondHalfPriceStrategy实现
- 客户端代码几乎不需要改动
4.3 里氏替换原则(LSP)
所有具体策略都是PromotionStrategy的子类型,可以互相替换而不影响程序正确性。比如测试时可以用MockStrategy替换真实策略。
5. 实战中的坑与解决方案
5.1 策略选择逻辑复杂化
反模式:在上下文类中添加复杂的策略选择逻辑,又变相引入了if-else。
解决方案:将策略选择逻辑外移到专门的工厂或路由类中,保持上下文简单。
5.2 策略参数传递问题
常见问题:不同策略需要不同的配置参数(如满减需要condition和reduction,折扣只需要rate)。
解决方案:
- 使用Builder模式构造策略对象
- 或者定义统一的参数对象,不同策略只使用自己需要的字段
5.3 与模板方法模式混淆
易混淆点:模板方法模式通过继承实现算法可变部分,策略模式通过组合实现。
判断标准:如果算法骨架固定只有某些步骤变化→用模板方法;如果整个算法都可替换→用策略模式。
6. 性能优化与最佳实践
6.1 策略对象缓存
高频使用的策略对象应该缓存:
public enum StrategyCache { INSTANCE; private final Map<String, PromotionStrategy> cache = new ConcurrentHashMap<>(); public PromotionStrategy getStrategy(String type) { return cache.computeIfAbsent(type, key -> { // 初始化策略对象 if("FULL_300_50".equals(key)) { return new FullReductionStrategy(new BigDecimal("300"), new BigDecimal("50")); } // 其他策略初始化... }); } }6.2 Lambda表达式简化
Java 8+可以用函数式接口简化策略模式:
public class PromotionContext { private Function<BigDecimal, BigDecimal> strategy; public PromotionContext(Function<BigDecimal, BigDecimal> strategy) { this.strategy = strategy; } public BigDecimal execute(BigDecimal price) { return strategy.apply(price); } } // 使用示例 PromotionContext context = new PromotionContext( price -> price.compareTo(new BigDecimal("300")) >= 0 ? price.subtract(new BigDecimal("50")) : price);6.3 策略模式与其他模式结合
常见组合方案:
- 策略+工厂模式:用工厂管理策略创建
- 策略+享元模式:共享无状态的策略对象
- 策略+装饰器模式:动态添加策略功能
7. 真实项目案例:支付网关设计
我在金融项目中设计的支付网关架构:
- 定义支付策略接口
public interface PaymentStrategy { PaymentResult execute(PaymentRequest request); boolean supports(PaymentType type); }- 实现各种支付方式
@Service public class AlipayStrategy implements PaymentStrategy { @Override public PaymentResult execute(PaymentRequest request) { // 调用支付宝SDK } @Override public boolean supports(PaymentType type) { return type == PaymentType.ALIPAY; } } @Service public class WechatPayStrategy implements PaymentStrategy {...}- 路由支付请求
@Service public class PaymentRouter { @Autowired private List<PaymentStrategy> strategies; public PaymentResult process(PaymentRequest request) { return strategies.stream() .filter(s -> s.supports(request.getType())) .findFirst() .orElseThrow(() -> new UnsupportedOperationException("不支持的支付方式")) .execute(request); } }这种设计让新增支付方式变得非常简单,只需添加新的策略实现类,无需修改现有代码。系统上线三年来,已经无缝接入了12种支付方式。