文章目录
前言
策略模式,就是把同一件事的不同做法分别封装起来,使用时选择其中一种。
电商场景中的例子
以电商中下单时计算优惠后的应付金额为例。假设一个订单每次只能选择一种优惠
- 优惠方式有:满 300 减 50、满 200 减 30、打九折...
- 订单原价都是400
- 得到的应付金额为:350、370、360...
假设商品价格和优惠使用资格已经由服务端确认。先定义一个统一接口,规定所有优惠策略都必须能根据原价计算应付金额:
java
public interface DiscountStrategy {
BigDecimal calculatePayAmount(BigDecimal originalAmount);
}
这个接口只规定需要做什么,具体怎么算,由实现类决定。金额使用 BigDecimal,避免直接使用 double 带来的二进制浮点精度问题。
接着,把两种优惠规则分别写成两个类。
java
// 策略一:满 300 减 50
public class FullReductionStrategy implements DiscountStrategy {
@Override
public BigDecimal calculatePayAmount(BigDecimal originalAmount) {
BigDecimal threshold = new BigDecimal("300");
BigDecimal reduction = new BigDecimal("50");
// compareTo 返回值 >= 0,表示原价 >= 300
if (originalAmount.compareTo(threshold) >= 0) {
//执行减法
return originalAmount.subtract(reduction);
}
// 不满足门槛,按原价支付
return originalAmount;
}
}
BigDecimal:double 使用二进制浮点数表示数值,无法精确表示某些十进制小数,因此计算结果可能出现微小误差。
传入 400,返回 350;传入 200,返回 200。满减门槛、减多少钱,都由这个策略负责。
java
// 策略二:打九折
public class PercentageDiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal calculatePayAmount(BigDecimal originalAmount) {
return originalAmount
.multiply(new BigDecimal("0.9"))
.setScale(2, RoundingMode.HALF_UP);
}
}
传入 400,返回 360.00。setScale(2, RoundingMode.HALF_UP) 表示按照这个示例约定,保留两位小数、四舍五入。
然后,订单服务通过统一接口调用优惠策略:
java
public class OrderService {
public BigDecimal calculateOrderAmount(
BigDecimal originalAmount,
DiscountStrategy strategy) {
// 订单服务把优惠计算交给传入的策略
BigDecimal payAmount =
strategy.calculatePayAmount(originalAmount);
// 后续可以使用 payAmount 保存订单等
return payAmount;
}
}
注意方法参数:
DiscountStrategy strategy
它声明的是接口类型,因此既能接收满减策略,也能接收折扣策略。这体现了 Java 的多态:调用相同的方法,实际执行哪个实现,取决于传入的对象。
实际调用时:
java
OrderService orderService = new OrderService();
BigDecimal originalAmount = new BigDecimal("400.00");
// 选择满减策略
BigDecimal amount1 = orderService.calculateOrderAmount(
originalAmount,
new FullReductionStrategy()
);
System.out.println(amount1); // 350.00
// 选择九折策略
BigDecimal amount2 = orderService.calculateOrderAmount(
originalAmount,
new PercentageDiscountStrategy()
);
System.out.println(amount2); // 360.00
可以把这几部分对应起来看:
| 代码 | 在策略模式中的角色 | 负责什么 |
|---|---|---|
| DiscountStrategy | 策略接口 | 统一优惠计算方法 |
| FullReductionStrategy | 具体策略 | 实现满减规则 |
| PercentageDiscountStrategy | 具体策略 | 实现折扣规则 |
| OrderService | 上下文,即使用策略的业务类 | 调用策略,完成订单金额计算 |

实际项目中,怎么根据用户选择找到策略?
例如,可以维护"优惠类型 → 策略对象"的映射:
java
Map<String, DiscountStrategy> strategies = Map.of(
"FULL_REDUCTION", new FullReductionStrategy(),
"PERCENTAGE_DISCOUNT", new PercentageDiscountStrategy()
);
// 假设服务端校验后,确定本次使用满减优惠
String promotionType = "FULL_REDUCTION";
// 根据类型找到策略对象
DiscountStrategy strategy = strategies.get(promotionType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的优惠类型");
}
// 统一执行
BigDecimal payAmount =
orderService.calculateOrderAmount(originalAmount, strategy);
这里,Map 负责找到哪个策略,策略对象负责执行具体算法。策略模式本身并不要求必须使用 Map,简单的选择逻辑也可以。
策略模式的优势
其优势主要体现在后续修改需求时:
- 增加优惠更方便。 新增"会员打八折",可以增加 MemberDiscountStrategy 并注册到映射中,订单服务的调用逻辑不用改。
- 修改规则更集中。 满减要改成"满 500 减 80",主要修改满减策略,不需要进入订单服务调整一大段优惠分支。
- 测试更独立。 可以单独测试满减策略在 299、300、301 元时是否计算正确,不必运行整个下单流程。