当前位置: 首页 > news >正文

Shoptnt 促销计算引擎详解:策略模式与责任链的完美融合

在电商系统中,促销计算是业务逻辑最复杂、变更最频繁的模块之一。它不仅需要处理多种促销类型(满减、折扣、优惠券等),还要管理它们之间的优先级和互斥关系。

Shoptnt 设计了一套基于 策略模式 (Strategy Pattern) 和 责任链模式 (Chain of Responsibility) 的促销计算引擎,实现了极高的灵活性和可扩展性。本文将深入剖析其核心实现,揭示其如何优雅地管理复杂的促销规则。

项目地址: https://gitee.com/bbc-se/shoptnt

一、 核心架构:PromotionHandler 策略接口

一切的核心是 PromotionHandler 接口。它定义了一个促销计算单元的契约,是一种典型的策略模式应用。

java

public interface PromotionHandler {// 策略方法:执行促销计算List<PromotionResult> execute(Promotion promotion, List<SkuDeal> skuDealList);// 标识策略类型:处理哪种促销(如 HALF_PRICE)PromotionTypeEnum promotionType();// 标识策略层级:处理哪个层级(SKU, SHOP, PLATFORM)PromotionLevel promotionLevel();
}

设计要点:

  • 单一职责: 每个 PromotionHandler 只负责一种特定类型促销的计算逻辑,如 HalfPriceHandler 只处理第二件半价。

  • 开闭原则: 新增促销类型时,只需实现一个新的 PromotionHandler,无需修改现有代码。

  • 明确标识: promotionType() 和 promotionLevel() 方法使得调度器可以精准地找到并调用对应的处理器。

二、 调度中心:PromotionCalculateClientImpl

PromotionCalculateClientImpl 是促销计算的调度中心上下文 (Context)。它的核心作用是收集所有 PromotionHandler 策略,并按需调用它们。

1. 自动收集所有策略:
通过 Spring 的依赖注入,所有实现了 PromotionHandler 的 Bean 都会被自动注入到 promotionHandlerList 中。

java

@Autowired
private List<PromotionHandler> promotionHandlerList; // 所有促销策略的集合

2. 分层过滤与执行:
在 calculateShopPromotion 方法中,调度器首先过滤出非平台级别的处理器(!handler.promotionLevel().equals(PromotionLevel.platform)),然后遍历这些处理器进行计算。

java

// 1. 过滤出需要的策略(店铺级和SKU级)
List<PromotionHandler> shopHandlerList = promotionHandlerList.stream().filter(handler -> !handler.promotionLevel().equals(PromotionLevel.platform)).collect(Collectors.toList());// 2. 遍历策略列表,让每个策略都尝试计算
for (PromotionHandler promotionHandler : shopHandlerList) {List<PromotionResult> resultList = calculateShopPromotion(promotionList, promotionHandler, skuList);promotionResultList.addAll(resultList);
}

3. 策略匹配:
在 calculateShopPromotion (私有方法) 中,调度器会遍历所有促销活动,将活动类型与处理器的类型进行匹配。只有匹配的处理器才会被执行。

java

private List<PromotionResult> calculateShopPromotion(List<Promotion> promotionList,PromotionHandler promotionHandler,List<SkuDeal> skuList) {for (Promotion promotion : promotionList) {// 关键:促销活动类型 必须 匹配 处理器类型if (promotion.getType().equals(promotionHandler.promotionType())) {// 匹配成功,执行该策略的计算逻辑List<PromotionResult> promotionResults = promotionHandler.execute(promotion, skuList);promotionResultList.addAll(promotionResults);}}
}

这个过程形成了一个隐式的责任链:调度器将促销活动和商品信息传递给一系列处理器,每个处理器只处理自己关心的那部分。

三、 策略实现:以 HalfPriceHandler 为例

让我们以 HalfPriceHandler 为例,看一个具体的策略是如何实现的。

1. 标识身份:

java

@Service
@Order(PromotionOrder.HalfPrice) // 定义计算优先级
public class HalfPriceHandler implements PromotionHandler {@Overridepublic PromotionTypeEnum promotionType() {return PromotionTypeEnum.HALF_PRICE; // 我负责处理第二件半价}@Overridepublic PromotionLevel promotionLevel() {return PromotionLevel.sku; // 我是SKU级别的活动}
}

@Order(PromotionOrder.HalfPrice) 注解至关重要,它定义了该处理器在 promotionHandlerList 中的执行顺序,确保了“单品优惠”先于“组合优惠”计算。

2. 核心计算逻辑 (execute 方法):

  • 遍历商品: 处理器会遍历传入的所有商品 (SkuDeal)。

  • 检查资格: 检查商品是否参与了当前的第二件半价活动 (promotion.getSkuIdList().contains(...))。

  • 计算优惠: 如果满足条件,则计算优惠金额。逻辑是:优惠金额 = (购买数量 / 2) * (单价 / 2)

  • 构建结果: 将计算结果封装成一个 SkuPromotionResult 对象并返回。注意:它修改了 SkuDeal 的 subtotal(小计金额),这个修改后的值会传递给后续的处理器,从而实现促销的叠加计算。

java

// 在 handle 方法中
double subtotal = skuDeal.getSubtotal(); // 获取当前小计(可能已被之前的处理器优惠过)
subtotal = CurrencyUtil.sub(subtotal, discount); // 减去本次优惠金额
skuDeal.setSubtotal(subtotal); // 设置新的小计,影响后续计算

四、 计算顺序的控制:@Order 注解

PromotionOrder 类定义了不同促销类型的执行顺序,这是保证复杂促销规则能正确叠加的关键。

java

public class PromotionOrder {public static final int Minus = 10;       // 单品立减public static final int Seckill = 15;     // 秒杀public static final int HalfPrice = 15;   // 第二件半价public static final int FullMinus = 25;   // 满减public static final int ShopCoupon = 30;  // 店铺券public static final int PlatformCoupon = 35; // 平台券
}

执行顺序规则:

  1. 价格直降型优先: 如 Minus(立减)、Seckill(秒杀)、HalfPrice(第二件半价)等直接修改商品单价的活动最先计算。

  2. 满减活动次之: FullMinus(满减)等基于总价条件的活动随后计算。

  3. 优惠券最后: ShopCoupon 和 PlatformCoupon 最后计算,因为它们通常是基于所有优惠后的最终金额进行减免。

这种顺序符合商业直觉:先享受单品折扣,再享受满减优惠,最后用券抵扣。

五、 结果的统一抽象:PromotionResult 体系

所有促销计算的结果都统一返回为 PromotionResult 或其子类 (SkuPromotionResultShopPromotionResultPlatformPromotionResult)。这种设计:

  • 统一了返回格式: 无论何种促销,应用端 (cart 模块) 都使用同一套接口来处理结果。

  • 包含了丰富信息: 不仅包含优惠金额 (cashBack),还包含赠品信息 (giftList)、运费减免 (isFreeFreight)、提示信息 (promotionTips) 等。

  • 支持多态: 使用 @JsonTypeInfo 注解,方便在序列化和反序列化时自动处理不同的子类。

总结:如何新增一个促销规则?

假设我们要增加一个“买三送一”的活动。

  1. 定义促销类型: 在 PromotionTypeEnum 中新增 BUY_THREE_GET_ONE

  2. 实现策略处理器: 创建一个新的 BuyThreeGetOneHandler 类,实现 PromotionHandler 接口。

    • 在 promotionType() 中返回 BUY_THREE_GET_ONE

    • 在 promotionLevel() 中返回 PromotionLevel.sku

    • 在 execute() 方法中实现“买三送一”的逻辑:计算应赠送的数量,并可能修改 SkuDeal 的数量或设置赠品信息到 PromotionResult 中。

  3. 定义执行顺序: 在 PromotionOrder 中为其定义一个顺序值(例如 18),位于单品折扣和满减之间。

  4. 完成! 由于调度器是自动收集所有 PromotionHandler 的,你的新处理器会自动被纳入计算流程,无需修改任何调度逻辑。

架构优势

  • 极致解耦: 计算逻辑 (promotion 模块) 与应用逻辑 (cart 模块) 完全分离,通过 PromotionResult DTO 进行通信。

  • 高可扩展性: 新增促销类型如同插拔组件,符合开闭原则。

  • 灵活的计算顺序: 通过 @Order 轻松管理复杂的优先级和叠加规则。

  • 易于测试: 每个 PromotionHandler 都可以被单独测试。

Shoptnt 的促销计算引擎是一个经典且优秀的设计范例,完美展示了如何用设计模式解决复杂的业务问题。欢迎访问项目源码深入学习:

https://gitee.com/bbc-se/shoptnt


文章转载自:

http://5I2qxJIv.wjLnz.cn
http://u6mrV0hm.wjLnz.cn
http://xmmiMPWK.wjLnz.cn
http://fty1RxCF.wjLnz.cn
http://gSAywwzf.wjLnz.cn
http://IrkmqSS9.wjLnz.cn
http://kUUymuts.wjLnz.cn
http://vq7ObxaO.wjLnz.cn
http://NKuDVqDY.wjLnz.cn
http://zCKMT9Sw.wjLnz.cn
http://r7mr6ojF.wjLnz.cn
http://3WZwsaAO.wjLnz.cn
http://7spC0Z5L.wjLnz.cn
http://1QtIEGRQ.wjLnz.cn
http://pwmWYLrd.wjLnz.cn
http://QSZlsuZ1.wjLnz.cn
http://2XF7FVcp.wjLnz.cn
http://2prM3le5.wjLnz.cn
http://iKudJhF5.wjLnz.cn
http://9Wj4PcQQ.wjLnz.cn
http://VMRm46iU.wjLnz.cn
http://rJ9up9PF.wjLnz.cn
http://LtH0Rb1D.wjLnz.cn
http://RxlPdynV.wjLnz.cn
http://Q98z3CfP.wjLnz.cn
http://wssJKQ4W.wjLnz.cn
http://0hNHI18P.wjLnz.cn
http://M6nm0On9.wjLnz.cn
http://PskHi1o5.wjLnz.cn
http://Eq3wxEYm.wjLnz.cn
http://www.dtcms.com/a/378109.html

相关文章:

  • 第 2 篇:Java 入门实战(JDK8 版)—— 编写第一个 Java 程序,理解基础运行逻辑
  • 人工智能深度学习——多层感知器(神经网络)
  • 【RelayMQ】基于 Java 实现轻量级消息队列(七)
  • 从任意Git服务迁移仓库
  • OpenCV:图像透视变换
  • 小程序原生实现音频播放器,下一首上一首切换,拖动进度条等功能
  • 前端查询条件是“0”几的时候查不到
  • openCV高阶操作之金字塔操作与直方图分析
  • 班级互动小程序(Python)
  • MongoDB面试集锦
  • 【JavaSE四天速通|第二篇】面向对象高级篇
  • 详细介绍一下 ​JSF(JavaServer Faces)
  • SpringCloud微服务网关Gateway
  • 跟做springboot尚品甄选项目(二)
  • 基于Mysql+SpringBoot+vue框架-大创管理系统源码
  • fastapi文档
  • vim指令
  • 【源码剖析】4-生产者-KafkaProducer分析
  • 事务方案选型全景图:金融与电商场景的实战差异与落地指南
  • 基于LSTM与3秒级Tick数据的金融时间序列预测实现
  • 第3节-使用表格数据-主键
  • 【C++练习】14.C++统计字符串中字母、数字、空格和其他字符的个数
  • ES6笔记5
  • 协议_https协议
  • 深入 Linux 文件系统:从数据存储到万物皆文件
  • 第十四届蓝桥杯青少组C++选拔赛[2023.1.15]第二部分编程题(1 、求十位数字)
  • CSS 属性概述
  • Ascend310B重构驱动run包
  • 碎片化采购是座金矿:数字化正重构电子元器件分销的价值链
  • 如何配置capacitor 打包的ios app固定竖屏展示?