【设计模型】策略模式

文章目录


前言

 策略模式,就是把同一件事的不同做法分别封装起来,使用时选择其中一种。


电商场景中的例子

以电商中下单时计算优惠后的应付金额为例。假设一个订单每次只能选择一种优惠

  • 优惠方式有:满 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 元时是否计算正确,不必运行整个下单流程。
相关推荐
FinelyYang12 小时前
考试系统简答题 AI 阅卷:覆盖度驱动、异步评分与人机协同落地实践
java·大数据·人工智能
hhhjhl12 小时前
Yup 跨端三方库仓颉适配全流程
java·开发语言
用户31268748772013 小时前
Java 对象内存布局:压缩指针、对齐填充与 JOL 实测
java
东华果汁哥13 小时前
【Spring AI 实战】从零开始调用大模型接口,构建 Java AI 应用
java·人工智能·spring
Song_RsHt13 小时前
Redis 与微服务,什么场景下无可替代
java·redis·后端·学习·微服务
学长毕设14 小时前
基于SpringBoot的建筑材料管理系统-附源码
java·spring boot·后端
摸爬滚打的小李14 小时前
关于 Arthas
java·后端
砚底藏山河14 小时前
python量化实操:冷热分层存储
java·开发语言·python·金融
yuniko-n15 小时前
【JUC】原子类
java
惊讶的猫15 小时前
Query 改写
java·rag