1. 策略模式概述
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户端。
在实际开发中,我们经常会遇到需要根据不同条件执行不同算法的情况。比如电商系统中的折扣计算、支付系统的支付方式选择、游戏中的角色技能系统等。策略模式通过将算法封装成独立的策略类,使得算法可以独立于客户端而变化。
策略模式的核心思想:将可变的部分(算法/行为)抽象出来,封装成接口,让客户端依赖于抽象而非具体实现。
2. 策略模式的结构与实现
2.1 UML类图解析
策略模式的典型UML类图包含三个核心角色:
- Context(环境类):持有一个策略类的引用,负责调用具体策略
- Strategy(抽象策略类):定义所有支持的算法的公共接口
- ConcreteStrategy(具体策略类):实现Strategy接口的具体算法
Context → Strategy Strategy ← ConcreteStrategyA Strategy ← ConcreteStrategyB2.2 代码实现示例
下面是一个完整的Java实现示例:
// 抽象策略接口 public interface DiscountStrategy { double applyDiscount(double originalPrice); } // 具体策略类:无折扣 public class NoDiscount implements DiscountStrategy { @Override public double applyDiscount(double originalPrice) { return originalPrice; } } // 具体策略类:固定折扣 public class FixedDiscount implements DiscountStrategy { private double discountAmount; public FixedDiscount(double discountAmount) { this.discountAmount = discountAmount; } @Override public double applyDiscount(double originalPrice) { return originalPrice - discountAmount; } } // 具体策略类:百分比折扣 public class PercentageDiscount implements DiscountStrategy { private double percentage; public PercentageDiscount(double percentage) { this.percentage = percentage; } @Override public double applyDiscount(double originalPrice) { return originalPrice * (1 - percentage/100); } } // 环境类 public class DiscountContext { private DiscountStrategy strategy; public void setStrategy(DiscountStrategy strategy) { this.strategy = strategy; } public double executeStrategy(double originalPrice) { return strategy.applyDiscount(originalPrice); } } // 客户端使用 public class Client { public static void main(String[] args) { DiscountContext context = new DiscountContext(); // 使用无折扣策略 context.setStrategy(new NoDiscount()); System.out.println("Price after no discount: " + context.executeStrategy(100)); // 使用固定折扣策略 context.setStrategy(new FixedDiscount(20)); System.out.println("Price after fixed discount: " + context.executeStrategy(100)); // 使用百分比折扣策略 context.setStrategy(new PercentageDiscount(10)); System.out.println("Price after percentage discount: " + context.executeStrategy(100)); } }2.3 实现要点分析
策略接口设计:策略接口应该足够抽象,能够涵盖所有可能的算法变体。接口方法命名要具有通用性,如
applyDiscount而不是calculatePercentageDiscount。上下文类设计:
- 通常通过构造函数或setter方法注入策略对象
- 可以结合工厂模式来创建策略对象
- 可以提供默认策略实现
策略对象管理:
- 策略对象通常是无状态的,可以被多个上下文共享
- 如果策略需要配置参数,可以通过构造函数传入
3. 策略模式的应用场景
3.1 电商系统中的折扣策略
电商平台通常有多种促销方式:
- 会员折扣(不同等级不同折扣)
- 满减优惠
- 限时折扣
- 组合优惠
使用策略模式可以灵活组合这些促销策略:
public interface PromotionStrategy { double applyPromotion(Order order); } public class CompositePromotion implements PromotionStrategy { private List<PromotionStrategy> strategies; public double applyPromotion(Order order) { double finalPrice = order.getOriginalPrice(); for(PromotionStrategy strategy : strategies) { finalPrice = strategy.applyPromotion(order); order.setCurrentPrice(finalPrice); } return finalPrice; } }3.2 支付系统设计
支付系统需要支持多种支付方式:
- 信用卡支付
- 支付宝
- 微信支付
- PayPal
- 银行转账
策略模式实现:
public interface PaymentStrategy { boolean pay(double amount); } public class PaymentContext { public boolean executePayment(PaymentStrategy strategy, double amount) { return strategy.pay(amount); } }3.3 游戏开发中的应用
游戏中的AI行为、角色技能等都可以使用策略模式:
public interface AIStrategy { void execute(); } public class AggressiveAI implements AIStrategy { public void execute() { // 攻击性行为逻辑 } } public class DefensiveAI implements AIStrategy { public void execute() { // 防御性行为逻辑 } } public class NPCAI { private AIStrategy strategy; public void setStrategy(AIStrategy strategy) { this.strategy = strategy; } public void performAction() { strategy.execute(); } }4. 策略模式的优缺点分析
4.1 优势
- 开闭原则:无需修改上下文即可引入新策略
- 消除条件语句:避免大量的if-else或switch-case语句
- 算法复用:相同策略可以被不同上下文使用
- 灵活性:运行时可以切换策略
- 单一职责:每个策略类只负责一个算法
4.2 局限性
- 客户端必须了解策略差异:客户端需要知道不同策略的区别以选择合适的策略
- 策略对象增多:当策略很多时,会产生大量策略类
- 通信开销:策略与上下文之间可能需要交换数据,增加复杂性
- 对象创建开销:频繁创建和销毁策略对象可能影响性能
5. 策略模式的最佳实践
5.1 与工厂模式结合
将策略的创建逻辑封装到工厂中:
public class StrategyFactory { public static DiscountStrategy getDiscountStrategy(String discountType) { switch(discountType) { case "FIXED": return new FixedDiscount(10); case "PERCENTAGE": return new PercentageDiscount(5); default: return new NoDiscount(); } } } // 客户端使用 DiscountStrategy strategy = StrategyFactory.getDiscountStrategy("FIXED"); context.setStrategy(strategy);5.2 使用枚举简化策略管理
public enum DiscountType { NONE { public DiscountStrategy getStrategy() { return new NoDiscount(); } }, FIXED { public DiscountStrategy getStrategy() { return new FixedDiscount(10); } }, PERCENTAGE { public DiscountStrategy getStrategy() { return new PercentageDiscount(5); } }; public abstract DiscountStrategy getStrategy(); } // 使用方式 context.setStrategy(DiscountType.FIXED.getStrategy());5.3 策略模式与Lambda表达式
在Java 8+中,可以使用函数式接口和Lambda简化策略模式:
@FunctionalInterface public interface DiscountStrategy { double applyDiscount(double originalPrice); } // 策略定义 DiscountStrategy noDiscount = price -> price; DiscountStrategy fixedDiscount = price -> price - 10; DiscountStrategy percentageDiscount = price -> price * 0.9; // 使用方式 context.setStrategy(fixedDiscount);6. 常见问题与解决方案
6.1 如何选择策略?
常见的策略选择方式:
- 客户端显式选择:由客户端代码直接指定
- 基于配置的选择:从配置文件或数据库读取策略类型
- 自动选择策略:根据上下文状态自动选择合适策略
// 自动选择策略示例 public DiscountStrategy autoSelectStrategy(Order order) { if(order.getTotalAmount() > 1000) { return new PercentageDiscount(10); } else if(order.getCustomer().isVIP()) { return new FixedDiscount(50); } else { return new NoDiscount(); } }6.2 策略之间如何共享数据?
- 通过上下文传递:将共享数据放在Context中
- 策略链模式:一个策略处理完后将结果传递给下一个策略
- 参数对象:将多个参数封装成一个参数对象传递
6.3 如何处理策略组合?
使用组合策略模式:
public class CompositeStrategy implements DiscountStrategy { private List<DiscountStrategy> strategies; public double applyDiscount(double originalPrice) { double currentPrice = originalPrice; for(DiscountStrategy strategy : strategies) { currentPrice = strategy.applyDiscount(currentPrice); } return currentPrice; } }7. 策略模式与其他模式的关系
7.1 策略模式 vs 状态模式
相似点:
- 都有上下文类和多个具体实现类
- 都通过委托来实现行为
不同点:
- 策略模式:客户端主动选择策略
- 状态模式:状态转换由内部条件决定
7.2 策略模式 vs 命令模式
相似点:
- 都将行为封装成对象
不同点:
- 策略模式:关注算法选择
- 命令模式:关注请求的封装和排队
7.3 策略模式 vs 模板方法模式
相似点:
- 都用于算法变化
不同点:
- 策略模式:通过对象组合实现
- 模板方法:通过类继承实现
8. 实际项目中的经验分享
8.1 性能优化技巧
- 策略对象复用:对于无状态策略,可以创建单例
- 策略缓存:使用缓存避免重复创建策略对象
- 延迟加载:策略按需加载,减少启动时间
public class StrategyCache { private static Map<String, DiscountStrategy> cache = new HashMap<>(); public static DiscountStrategy getStrategy(String type) { if(!cache.containsKey(type)) { synchronized (cache) { if(!cache.containsKey(type)) { cache.put(type, createStrategy(type)); } } } return cache.get(type); } private static DiscountStrategy createStrategy(String type) { // 创建策略的逻辑 } }8.2 测试策略
- 单元测试每个策略:确保每个策略独立正确
- 上下文测试:验证上下文是否正确使用策略
- 策略切换测试:测试运行时策略切换的正确性
@Test public void testStrategySwitch() { DiscountContext context = new DiscountContext(); context.setStrategy(new FixedDiscount(10)); assertEquals(90, context.executeStrategy(100), 0.001); context.setStrategy(new PercentageDiscount(10)); assertEquals(90, context.executeStrategy(100), 0.001); }8.3 常见陷阱
- 策略膨胀:避免创建过多细粒度策略
- 过度设计:简单条件判断可能比策略模式更合适
- 上下文臃肿:不要把太多逻辑放在Context中
- 策略泄漏:避免策略类依赖Context内部状态
在实际项目中,我经常看到开发者过度使用策略模式。一个经验法则是:当你有多个条件分支处理相似但不同的算法,并且这些算法可能在将来会变化或扩展时,才考虑使用策略模式。对于简单、稳定的条件逻辑,简单的if-else可能更合适。