1. 策略模式基础与if-else困境
在Java开发中,我们经常会遇到类似这样的代码:
java复制public double calculatePrice(String userType, double originalPrice) {
if ("VIP".equals(userType)) {
return originalPrice * 0.8;
} else if ("Regular".equals(userType)) {
return originalPrice * 0.9;
} else if ("NewUser".equals(userType)) {
return originalPrice * 0.85;
} else {
return originalPrice;
}
}
这种if-else结构看似简单直接,但随着业务逻辑的复杂化,会暴露出几个严重问题:
- 违反开闭原则:每次新增用户类型都需要修改现有方法
- 可读性差:当条件分支超过10个时,代码变得难以阅读和维护
- 测试困难:每个分支都需要单独测试用例,测试覆盖率难以保证
- 职责过重:一个方法承担了过多不同类型的计算逻辑
我在电商系统重构中就遇到过这样的案例:最初只有3种用户类型,两年后发展到15种,calculatePrice方法膨胀到300多行,每次修改都提心吊胆,生怕影响其他分支的逻辑。
2. 策略模式实现步骤详解
2.1 定义策略接口
首先创建一个策略接口,定义所有具体策略类必须实现的方法:
java复制public interface DiscountStrategy {
/**
* 计算最终价格
* @param originalPrice 原始价格
* @return 折扣后的价格
*/
double applyDiscount(double originalPrice);
/**
* 是否适用于当前用户类型
* @param userType 用户类型
* @return 是否适用
*/
boolean supports(String userType);
}
这里我特意设计了supports方法而不是简单的getUserType,因为实际业务中策略匹配条件可能更复杂,比如需要同时考虑用户类型和订单金额。
2.2 实现具体策略类
针对每种折扣类型创建具体策略类:
java复制public class VipDiscountStrategy implements DiscountStrategy {
@Override
public double applyDiscount(double originalPrice) {
return originalPrice * 0.8;
}
@Override
public boolean supports(String userType) {
return "VIP".equals(userType);
}
}
public class RegularDiscountStrategy implements DiscountStrategy {
@Override
public double applyDiscount(double originalPrice) {
return originalPrice * 0.9;
}
@Override
public boolean supports(String userType) {
return "Regular".equals(userType);
}
}
2.3 创建策略上下文
策略上下文负责管理策略对象,并根据输入选择合适的策略:
java复制public class DiscountContext {
private List<DiscountStrategy> strategies;
public DiscountContext() {
// 初始化所有策略
this.strategies = new ArrayList<>();
this.strategies.add(new VipDiscountStrategy());
this.strategies.add(new RegularDiscountStrategy());
this.strategies.add(new NewUserDiscountStrategy());
}
public double calculatePrice(String userType, double originalPrice) {
for (DiscountStrategy strategy : strategies) {
if (strategy.supports(userType)) {
return strategy.applyDiscount(originalPrice);
}
}
return originalPrice; // 默认策略
}
}
在实际项目中,我通常会使用Spring的依赖注入来管理策略对象,而不是在构造函数中硬编码:
java复制@Service
public class DiscountContext {
@Autowired
private List<DiscountStrategy> strategies;
// 其他代码不变
}
3. 高级应用与优化技巧
3.1 策略模式的Spring Boot集成
在Spring Boot项目中,我们可以利用自动装配特性更优雅地实现策略模式:
- 给每个策略类添加@Component注解
- 使用@Autowired自动注入策略列表
- 通过@PostConstruct初始化策略映射
java复制@Service
public class DiscountService {
@Autowired
private List<DiscountStrategy> strategies;
private Map<String, DiscountStrategy> strategyMap;
@PostConstruct
public void init() {
strategyMap = strategies.stream()
.flatMap(strategy -> strategy.supportedTypes()
.stream()
.map(type -> new AbstractMap.SimpleEntry<>(type, strategy)))
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(existing, replacement) -> existing));
}
public double calculatePrice(String userType, double price) {
DiscountStrategy strategy = strategyMap.getOrDefault(userType, DefaultStrategy.INSTANCE);
return strategy.applyDiscount(price);
}
}
3.2 策略工厂模式
当策略对象的创建逻辑比较复杂时,可以引入工厂模式:
java复制public class DiscountStrategyFactory {
private static final Map<String, Supplier<DiscountStrategy>> STRATEGY_MAP = new HashMap<>();
static {
STRATEGY_MAP.put("VIP", VipDiscountStrategy::new);
STRATEGY_MAP.put("Regular", RegularDiscountStrategy::new);
STRATEGY_MAP.put("NewUser", NewUserDiscountStrategy::new);
}
public static DiscountStrategy getStrategy(String userType) {
Supplier<DiscountStrategy> supplier = STRATEGY_MAP.get(userType);
return supplier != null ? supplier.get() : DefaultStrategy.INSTANCE;
}
}
这种方式的优势在于:
- 延迟创建策略对象,减少内存占用
- 可以动态注册新策略
- 策略创建逻辑集中管理
3.3 策略模式与Lambda表达式
Java 8以后,对于简单的策略,可以直接使用Lambda表达式:
java复制Map<String, DoubleUnaryOperator> strategyMap = new HashMap<>();
strategyMap.put("VIP", price -> price * 0.8);
strategyMap.put("Regular", price -> price * 0.9);
strategyMap.put("NewUser", price -> price * 0.85);
public double calculatePrice(String userType, double price) {
DoubleUnaryOperator strategy = strategyMap.getOrDefault(userType, p -> p);
return strategy.applyAsDouble(price);
}
这种方式简洁高效,适合策略逻辑简单的场景。我在处理一些临时性的营销活动时经常使用这种方法。
4. 实战案例:电商促销系统重构
4.1 重构前代码分析
这是我最近重构的一个电商系统中的促销计算模块原始代码:
java复制public BigDecimal calculateDiscount(String userType, BigDecimal amount,
boolean isHoliday, boolean isBirthday) {
if ("VIP".equals(userType)) {
if (isHoliday) {
return amount.multiply(BigDecimal.valueOf(0.7));
} else if (isBirthday) {
return amount.multiply(BigDecimal.valueOf(0.65));
} else {
return amount.multiply(BigDecimal.valueOf(0.8));
}
} else if ("Regular".equals(userType)) {
if (isHoliday) {
return amount.multiply(BigDecimal.valueOf(0.85));
} else {
return amount.multiply(BigDecimal.valueOf(0.9));
}
} else if ("NewUser".equals(userType)) {
return amount.multiply(BigDecimal.valueOf(0.85));
} else {
return amount;
}
}
这段代码存在的主要问题:
- 嵌套条件判断难以维护
- 折扣逻辑分散在各处
- 新增促销类型需要修改核心方法
- 无法灵活组合促销策略
4.2 策略模式重构方案
我将其重构为基于策略模式的解决方案:
- 定义促销策略接口:
java复制public interface PromotionStrategy {
boolean supports(UserContext context);
BigDecimal apply(BigDecimal amount, UserContext context);
}
@Data
public class UserContext {
private String userType;
private boolean isHoliday;
private boolean isBirthday;
// 其他上下文信息...
}
- 实现具体策略类:
java复制public class VipHolidayStrategy implements PromotionStrategy {
@Override
public boolean supports(UserContext context) {
return "VIP".equals(context.getUserType()) && context.isHoliday();
}
@Override
public BigDecimal apply(BigDecimal amount, UserContext context) {
return amount.multiply(BigDecimal.valueOf(0.7));
}
}
public class VipBirthdayStrategy implements PromotionStrategy {
@Override
public boolean supports(UserContext context) {
return "VIP".equals(context.getUserType()) && context.isBirthday();
}
@Override
public BigDecimal apply(BigDecimal amount, UserContext context) {
return amount.multiply(BigDecimal.valueOf(0.65));
}
}
- 创建策略组合器:
java复制public class PromotionEngine {
private List<PromotionStrategy> strategies;
public PromotionEngine(List<PromotionStrategy> strategies) {
this.strategies = strategies;
}
public BigDecimal calculateDiscount(BigDecimal amount, UserContext context) {
for (PromotionStrategy strategy : strategies) {
if (strategy.supports(context)) {
amount = strategy.apply(amount, context);
}
}
return amount;
}
}
4.3 重构效果对比
| 指标 | 重构前 | 重构后 |
|---|---|---|
| 代码行数 | 45行 | 分散到多个15-20行的类 |
| 可维护性 | 差,修改风险高 | 好,修改隔离 |
| 可扩展性 | 需要修改核心方法 | 新增策略类即可 |
| 可测试性 | 需要复杂测试用例 | 每个策略可单独测试 |
| 性能 | 略快 | 略慢(多对象创建) |
在实际项目中,重构后的系统在三个月内新增了5种促销策略,都没有修改原有代码,只是新增了策略类。测试覆盖率也从60%提升到了85%以上。
5. 策略模式的最佳实践与陷阱
5.1 何时使用策略模式
根据我的经验,以下场景特别适合使用策略模式:
- 业务规则经常变化:如促销活动、计费规则等
- 需要动态切换算法:如排序策略、压缩算法等
- 替代复杂条件判断:当if-else超过3层嵌套时
- 需要隔离不同实现:如多支付渠道、多消息通知方式
5.2 常见陷阱与解决方案
-
策略类爆炸:
- 问题:每个小变化都导致新策略类
- 方案:合理合并相似策略,使用参数化策略
-
上下文信息过多:
- 问题:策略接口需要传递大量参数
- 方案:封装上下文对象,如前面的UserContext
-
性能问题:
- 问题:频繁创建策略对象
- 方案:使用享元模式或对象池
-
策略选择复杂:
- 问题:策略选择逻辑变得复杂
- 方案:引入责任链模式或规则引擎
5.3 与其他模式的配合
在实际项目中,策略模式常与其他模式配合使用:
- 工厂模式:创建策略对象
- 组合模式:组合多个策略
- 模板方法模式:定义策略骨架
- 装饰者模式:动态添加策略行为
例如,在实现一个复杂的定价系统时,我使用了这样的结构:
code复制PricingEngine (上下文)
├── BasePriceStrategy
├── DiscountStrategy (策略接口)
│ ├── VipDiscountStrategy
│ ├── CouponDiscountStrategy
│ └── PromotionDiscountStrategy
└── TaxStrategy (策略接口)
├── DomesticTaxStrategy
└── InternationalTaxStrategy
这种设计允许灵活组合不同的定价策略,同时保持代码的清晰和可维护性。
