一、计算逻辑的类结构图
在这张图里,顶层接口 RuleTemplate 定义了 calculate 方法,抽象模板类 AbstractRuleTemplate 将通用的模板计算逻辑在 calculate 方法中实现,同时它还定义了一个抽象方法 calculateNewPrice 作为子类的扩展点。各个具体的优惠计算类通过继承 AbstractRuleTemplate,并实现 calculateNewPrice 来编写自己的优惠计算方式。
二、代码实现
1、RuleTemplate.java
public interface RuleTemplate {
// 计算优惠券
ShoppingCart calculate(ShoppingCart settlement);
}
2、AbstractRuleTemplate.java
public ShoppingCart calculate(ShoppingCart order) {
// 获取订单总价
Long orderTotalAmount = getTotalPrice(order.getProducts());
// 获取以shopId为维度的总价统计
Map<Long, Long> sumAmount = getTotalPriceGroupByShop(order.getProducts());
CouponTemplateInfo template = order.getCouponInfos().get(0).getTemplate();
// 最低消费限制
Long threshold = template.getRule().getDiscount().getThreshold();
// 优惠金额或者打折比例
Long quota = template.getRule().getDiscount().getQuota();
// 如果优惠券未指定shopId,则shopTotalAmount=orderTotalAmount
// 如果指定了shopId,则shopTotalAmount=对应门店下商品总价
Long shopId = template.getShopId();
Long shopTotalAmount = (shopId == null) ? orderTotalAmount : sumAmount.get(shopId);
// 如果不符合优惠券使用标准, 则直接按原价走,不使用优惠券
if (shopTotalAmount == null || shopTotalAmount < threshold) {
log.debug("Totals of amount not meet");
order.setCost(orderTotalAmount);
order.setCouponInfos(Collections.emptyList());
return order;
}
// 子类中实现calculateNewPrice计算新的价格
Long newCost = calculateNewPrice(orderTotalAmount, shopTotalAmount, quota);
if (newCost < minCost()) {
newCost = minCost();
}
order.setCost(newCost);
log.debug("original price={}, new price={}", orderTotalAmount, newCost);
return order;
}
3、子类
MoneyOffTemplate.java
@Slf4j
@Component
public class MoneyOffTemplate extends AbstractRuleTemplate implements RuleTemplate {
@Override
protected Long calculateNewPrice(Long totalAmount, Long shopAmount, Long quota) {
// benefitAmount是扣减的价格
// 如果当前门店的商品总价<quota,那么最多只能扣减shopAmount的钱数
Long benefitAmount = shopAmount < quota ? shopAmount : quota;
return totalAmount - benefitAmount;
}
}
4、工厂类
CouponTemplateFactory.java
@Component
@Slf4j
public class CouponTemplateFactory {
@Autowired
private MoneyOffTemplate moneyOffTemplate;
@Autowired
private DiscountTemplate discountTemplate;
@Autowired
private RandomReductionTemplate randomReductionTemplate;
@Autowired
private LonelyNightTemplate lonelyNightTemplate;
@Autowired
private DummyTemplate dummyTemplate;
@Autowired
private AntiPauTemplate antiPauTemplate;
public RuleTemplate getTemplate(ShoppingCart order) {
// 不使用优惠券
if (CollectionUtils.isEmpty(order.getCouponInfos())) {
// dummy模板直接返回原价,不进行优惠计算
return dummyTemplate;
}
// 获取优惠券的类别
// 目前每个订单只支持单张优惠券
CouponTemplateInfo template = order.getCouponInfos().get(0).getTemplate();
CouponType category = CouponType.convert(template.getType());
switch (category) {
// 订单满减券
case MONEY_OFF:
return moneyOffTemplate;
// 随机立减券
case RANDOM_DISCOUNT:
return randomReductionTemplate;
// 午夜下单优惠翻倍
case LONELY_NIGHT_MONEY_OFF:
return lonelyNightTemplate;
// 打折券
case DISCOUNT:
return discountTemplate;
case ANTI_PUA:
return antiPauTemplate;
// 未知类型的券模板
default:
return dummyTemplate;
}
}
}
5、使用
CouponCalculationServiceImpl.java
@Autowired
private CouponTemplateFactory couponProcessorFactory;
// 优惠券结算
// 这里通过Factory类决定使用哪个底层Rule,底层规则对上层透明
@Override
public ShoppingCart calculateOrderPrice(@RequestBody ShoppingCart cart) {
log.info("calculate order price: {}", JSON.toJSONString(cart));
RuleTemplate ruleTemplate = couponProcessorFactory.getTemplate(cart);
return ruleTemplate.calculate(cart);
}