AI摘要
文章以电商折扣计算为例,展示用策略模式替代臃肿if-else:定义DiscountStrategy接口,各等级实现类自封装规则;Spring自动注入列表,工厂按userLevel匹配策略。重构后新增等级零改动原代码,职责单一、测试便利,符合开闭原则。
面对需求中越来越多的“类型”,if-else分支无限膨胀,代码难以阅读、测试和维护。本文分享我如何使用策略模式,将一段处理不同用户等级折扣的“屎山”代码,重构得清晰、优雅且易于扩展。
- “屎山”代码现身说法
- 业务背景: 电商系统的折扣计算,不同用户等级(普通、白银、黄金、钻石)享受不同的折扣。
- 重构前的代码(典型的面向过程编程):
@Service
public class DiscountService {
public BigDecimal calculateDiscount(String userLevel, BigDecimal originalPrice) {
// 等级校验
if (userLevel == null) {
throw new IllegalArgumentException("用户等级不能为空");
}
// 庞大的if-else分支
if ("NORMAL".equals(userLevel)) {
return originalPrice.multiply(new BigDecimal("0.99"));
} else if ("SILVER".equals(userLevel)) {
return originalPrice.multiply(new BigDecimal("0.95"));
} else if ("GOLD".equals(userLevel)) {
// 黄金会员可能有额外的满减逻辑,让代码更混乱
BigDecimal discountedPrice = originalPrice.multiply(new BigDecimal("0.90"));
if (discountedPrice.compareTo(new BigDecimal("100")) > 0) {
discountedPrice = discountedPrice.subtract(new BigDecimal("10"));
}
return discountedPrice;
} else if ("DIAMOND".equals(userLevel)) {
return originalPrice.multiply(new BigDecimal("0.85"));
} else {
throw new IllegalArgumentException("不支持的会员等级: " + userLevel);
}
}
}痛点分析:
- 违反开闭原则: 新增一个“王者”会员等级,必须修改这个核心方法,容易引入Bug。
- 可读性差: 方法冗长,各个等级的逻辑耦合在一起。
- 可测试性差: 很难为每个等级的逻辑编写独立的单元测试。
- 职责不清: 一个方法承担了所有等级的计算逻辑。
重构实施:策略模式登场
- 第一步:定义策略接口。 抽象出折扣计算的共同行为。
/**
* 折扣策略接口
*/
public interface DiscountStrategy {
/**
* 判断该策略是否支持给定的用户等级
*/
boolean supports(String userLevel);
/**
* 计算折扣价
* @param originalPrice 原价
* @return 折扣后的价格
*/
BigDecimal apply(BigDecimal originalPrice);
}第二步:实现具体策略类。 每个等级一个类,职责单一。
/**
* 普通会员策略
*/
@Component // 关键:让Spring管理成为Bean
public class NormalDiscountStrategy implements DiscountStrategy {
@Override
public boolean supports(String userLevel) {
return "NORMAL".equals(userLevel);
}
@Override
public BigDecimal apply(BigDecimal originalPrice) {
return originalPrice.multiply(new BigDecimal("0.99"));
}
}
/**
* 黄金会员策略(包含复杂逻辑)
*/
@Component
public class GoldDiscountStrategy implements DiscountStrategy {
@Override
public boolean supports(String userLevel) {
return "GOLD".equals(userLevel);
}
@Override
public BigDecimal apply(BigDecimal originalPrice) {
BigDecimal discountedPrice = originalPrice.multiply(new BigDecimal("0.90"));
// 黄金会员的专属满减逻辑被隔离在此
if (discountedPrice.compareTo(new BigDecimal("100")) > 0) {
discountedPrice = discountedPrice.subtract(new BigDecimal("10"));
}
return discountedPrice;
}
}
// ... 其他策略类类似第三步:创建策略工厂(核心)。 利用Spring的依赖注入,自动收集所有策略。
@Component
public class DiscountStrategyFactory {
// Spring会自动将DiscountStrategy的所有实现类注入到这个List中
private final List<DiscountStrategy> strategies;
// 构造器注入
public DiscountStrategyFactory(List<DiscountStrategy> strategies) {
this.strategies = strategies;
}
/**
* 根据用户等级获取对应的策略
*/
public DiscountStrategy getStrategy(String userLevel) {
return strategies.stream()
.filter(s -> s.supports(userLevel))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("不支持的会员等级: " + userLevel));
}
}第四步:重构核心服务类。 变得极其简洁。
@Service
public class DiscountService {
private final DiscountStrategyFactory factory;
// 构造器注入工厂
public DiscountService(DiscountStrategyFactory factory) {
this.factory = factory;
}
public BigDecimal calculateDiscount(String userLevel, BigDecimal originalPrice) {
// 1. 参数校验(可选,也可放在策略中)
if (userLevel == null) {
throw new IllegalArgumentException("用户等级不能为空");
}
// 2. 通过工厂获取策略
DiscountStrategy strategy = factory.getStrategy(userLevel);
// 3. 执行策略
return strategy.apply(originalPrice);
}
}- 重构后的巨大优势
- 符合开闭原则: 新增会员等级,只需创建一个新的
DiscountStrategy实现类即可。DiscountService和DiscountStrategyFactory完全无需修改。 - 代码清晰,职责分离: 每个策略类只关心自己的计算逻辑,
DiscountService只负责调度,代码可读性极大提升。 - 易于单元测试: 可以轻松地单独测试
GoldDiscountStrategy的复杂满减逻辑,而无需关心其他等级。 - 易于扩展: 如果需要从数据库或配置中心加载折扣规则,只需要修改具体的策略实现类,调度逻辑不受影响。
- 更进一步:与工厂模式/Spring的强大结合
- 可以不需要
supports方法,而是通过注解(如@DiscountType("GOLD"))来标识策略类型,工厂通过扫描注解来建立映射关系Map<String, DiscountStrategy>,这样查找效率更高(O(1))。