1. 策略模式初探:为什么我们需要它?
记得刚入行那会儿,我接手了一个电商促销模块的需求。最初简单粗暴地用if-else堆砌了满减、折扣、赠品等各种促销逻辑,结果每次新增促销类型都要修改核心代码,测试同事见到我就躲。直到 mentor 扔给我一本《设计模式》,才明白策略模式正是解决这类问题的银弹。
策略模式(Strategy Pattern)本质上是一种行为设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户端。就像我们玩游戏时可以随时切换不同的武器,而角色本身不需要任何改变。
关键理解:策略模式不是用来解决"有没有"的问题,而是解决"多选一"的场景。当你的代码中出现大量条件判断语句,且这些判断都是为了实现同一功能的不同变体时,就该考虑策略模式了。
2. 模式结构深度拆解
2.1 UML类图解析
标准的策略模式包含三个核心角色:
- Context(环境类):持有一个Strategy的引用,负责与客户端交互
- Strategy(抽象策略):定义算法族的公共接口
- ConcreteStrategy(具体策略):实现具体的算法
java复制// 抽象策略接口
public interface DiscountStrategy {
double applyDiscount(double originalPrice);
}
// 具体策略实现
public class FullReductionStrategy implements DiscountStrategy {
private double condition;
private double reduction;
public FullReductionStrategy(double condition, double reduction) {
this.condition = condition;
this.reduction = reduction;
}
@Override
public double applyDiscount(double originalPrice) {
return originalPrice >= condition ? originalPrice - reduction : originalPrice;
}
}
// 环境类
public class DiscountContext {
private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double executeStrategy(double price) {
return strategy.applyDiscount(price);
}
}
2.2 与状态模式的本质区别
很多初学者容易混淆策略模式和状态模式,它们的UML类图确实相似,但意图完全不同:
- 策略模式:客户端主动选择算法
- 状态模式:状态转移由内部条件触发
举个例子:支付方式选择是策略模式(用户主动选择微信/支付宝),而订单状态流转是状态模式(系统根据条件自动从"待支付"变为"已支付")。
3. 实战:电商促销系统改造
3.1 原始代码的典型问题
先看改造前的典型代码:
java复制public class PromotionService {
public double applyPromotion(String type, double price) {
if ("FULL_REDUCTION".equals(type)) {
// 满减逻辑
return price > 100 ? price - 20 : price;
} else if ("DISCOUNT".equals(type)) {
// 折扣逻辑
return price * 0.8;
} else if ("GIFT".equals(type)) {
// 赠品逻辑
System.out.println("赠送小礼品");
return price;
}
throw new IllegalArgumentException("Unsupported promotion type");
}
}
这种写法存在三大痛点:
- 违反开闭原则(OCP) - 新增促销类型需要修改已有代码
- 方法过于臃肿 - 各种促销逻辑耦合在一起
- 难以单元测试 - 每种促销策略无法独立测试
3.2 策略模式改造步骤
步骤1:定义策略接口
java复制public interface PromotionStrategy {
String getType();
double applyPromotion(double price);
}
步骤2:实现具体策略
java复制public class FullReductionStrategy implements PromotionStrategy {
@Override
public String getType() {
return "FULL_REDUCTION";
}
@Override
public double applyPromotion(double price) {
return price > 100 ? price - 20 : price;
}
}
// 其他策略类似实现...
步骤3:创建策略工厂
java复制public class PromotionStrategyFactory {
private static final Map<String, PromotionStrategy> STRATEGIES = new HashMap<>();
static {
STRATEGIES.put("FULL_REDUCTION", new FullReductionStrategy());
STRATEGIES.put("DISCOUNT", new DiscountStrategy());
// 注册更多策略...
}
public static PromotionStrategy getStrategy(String type) {
return STRATEGIES.get(type);
}
// 支持动态注册新策略
public static void registerStrategy(String type, PromotionStrategy strategy) {
STRATEGIES.put(type, strategy);
}
}
步骤4:改造服务类
java复制public class PromotionService {
public double applyPromotion(String type, double price) {
PromotionStrategy strategy = PromotionStrategyFactory.getStrategy(type);
if (strategy == null) {
throw new IllegalArgumentException("Unsupported promotion type");
}
return strategy.applyPromotion(price);
}
}
3.3 改造后的优势对比
| 维度 | 改造前 | 改造后 |
|---|---|---|
| 扩展性 | 修改原类 | 新增策略类即可 |
| 可维护性 | 逻辑耦合 | 职责单一 |
| 可测试性 | 需整体测试 | 可单独测试每个策略 |
| 运行时灵活性 | 需重新部署 | 可动态注册新策略 |
4. 高级应用技巧
4.1 结合Spring框架的最佳实践
在实际Spring项目中,我们可以利用依赖注入更优雅地管理策略:
java复制@Service
public class PromotionService {
@Autowired
private Map<String, PromotionStrategy> strategyMap; // Spring会自动按bean name注入
public double applyPromotion(String type, double price) {
PromotionStrategy strategy = strategyMap.get(type + "Strategy");
if (strategy == null) {
throw new IllegalArgumentException("Unsupported promotion type");
}
return strategy.applyPromotion(price);
}
}
// 策略实现类加上@Component注解
@Component("fullReductionStrategy")
public class FullReductionStrategy implements PromotionStrategy {
// 实现略...
}
4.2 使用函数式接口简化
Java 8以后,策略接口可以用函数式接口简化:
java复制@FunctionalInterface
public interface PromotionStrategy {
double apply(double price);
// 可以定义默认方法
default String getDescription() {
return "促销策略";
}
}
// 使用示例
PromotionStrategy discountStrategy = price -> price * 0.8;
PromotionStrategy fullReductionStrategy = price -> price > 100 ? price - 20 : price;
4.3 策略组合模式
有时需要组合多个策略,比如先打折再满减:
java复制public class CompositePromotionStrategy implements PromotionStrategy {
private List<PromotionStrategy> strategies;
public CompositePromotionStrategy(PromotionStrategy... strategies) {
this.strategies = Arrays.asList(strategies);
}
@Override
public double applyPromotion(double price) {
double result = price;
for (PromotionStrategy strategy : strategies) {
result = strategy.applyPromotion(result);
}
return result;
}
}
5. 避坑指南与性能优化
5.1 常见实现误区
-
策略类有状态:策略类应该是无状态的,如果需要配置参数,应该通过构造函数注入
java复制// 错误示范 public class DiscountStrategy { private double rate; public void setRate(double rate) { this.rate = rate; } // ... } // 正确做法 public class DiscountStrategy { private final double rate; public DiscountStrategy(double rate) { this.rate = rate; } // ... } -
过度设计:简单业务直接if-else可能更合适,不要为了模式而模式
-
策略选择逻辑复杂:如果策略选择本身就很复杂,可能需要结合工厂模式
5.2 性能优化建议
-
策略对象复用:如果策略无状态,应该共享实例而不是频繁创建
java复制// 在工厂中缓存实例 public class StrategyFactory { private static final FullReductionStrategy FULL_REDUCTION = new FullReductionStrategy(); // ... } -
避免策略膨胀:当策略类过多时,考虑使用组合模式或责任链模式
-
并发安全:如果策略有可变状态,需要处理好线程安全问题
6. 真实业务场景扩展
6.1 支付网关集成
支付渠道选择是策略模式的经典场景:
java复制public interface PaymentStrategy {
boolean pay(BigDecimal amount);
}
public class AlipayStrategy implements PaymentStrategy {
@Override
public boolean pay(BigDecimal amount) {
// 调用支付宝SDK
}
}
public class PaymentService {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public boolean executePayment(BigDecimal amount) {
return strategy.pay(amount);
}
}
6.2 物流运费计算
不同物流公司的运费计算规则各异:
java复制public interface ShippingStrategy {
BigDecimal calculateFee(Order order);
}
public class SFShipping implements ShippingStrategy {
@Override
public BigDecimal calculateFee(Order order) {
// 顺丰计价逻辑
}
}
// 使用示例
ShippingStrategy strategy = new SFShipping();
BigDecimal fee = strategy.calculateFee(currentOrder);
6.3 游戏技能系统
游戏角色的不同技能可以看作是不同的策略:
java复制public interface SkillStrategy {
void execute(Character caster, Character target);
}
public class FireBallStrategy implements SkillStrategy {
@Override
public void execute(Character caster, Character target) {
// 火球术效果逻辑
}
}
// 战斗中切换技能
player.setCurrentSkill(new FireBallStrategy());
player.attack(enemy);
7. 测试策略模式的正确姿势
7.1 单元测试策略类
每个策略类应该独立测试:
java复制public class FullReductionStrategyTest {
@Test
public void testApplyDiscount_WhenPriceMeetsCondition_ShouldReduce() {
FullReductionStrategy strategy = new FullReductionStrategy(100, 20);
assertEquals(80, strategy.applyDiscount(100), 0.001);
}
@Test
public void testApplyDiscount_WhenPriceNotMeet_ShouldNotReduce() {
FullReductionStrategy strategy = new FullReductionStrategy(100, 20);
assertEquals(90, strategy.applyDiscount(90), 0.001);
}
}
7.2 集成测试上下文类
测试上下文类与策略的交互:
java复制public class DiscountContextTest {
@Test
public void testExecuteStrategy_ShouldDelegateToCurrentStrategy() {
DiscountContext context = new DiscountContext();
context.setStrategy(new FullReductionStrategy(100, 20));
assertEquals(80, context.executeStrategy(100), 0.001);
}
}
7.3 模拟策略切换场景
验证策略动态切换的正确性:
java复制@Test
public void testStrategySwitching() {
DiscountContext context = new DiscountContext();
// 初始使用满减策略
context.setStrategy(new FullReductionStrategy(100, 20));
assertEquals(80, context.executeStrategy(100), 0.001);
// 切换为折扣策略
context.setStrategy(new DiscountStrategy(0.8));
assertEquals(80, context.executeStrategy(100), 0.001);
}
8. 从策略模式到领域驱动设计
策略模式在DDD中可以很好地实现领域策略:
java复制// 领域服务中使用策略
public class OrderService {
private final PricingStrategy pricingStrategy;
public OrderService(PricingStrategy pricingStrategy) {
this.pricingStrategy = pricingStrategy;
}
public Order createOrder(Customer customer, List<Product> products) {
BigDecimal total = pricingStrategy.calculateTotal(products);
// 其他订单创建逻辑...
}
}
// 会员定价策略
public class VipPricingStrategy implements PricingStrategy {
@Override
public BigDecimal calculateTotal(List<Product> products) {
return products.stream()
.map(p -> p.getPrice().multiply(BigDecimal.valueOf(0.9)))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
这种实现方式使得定价策略可以独立演化,同时保持领域模型的纯净性。