Spring中策略模式模拟优惠券使用,解耦代码!
文章目录
- 1.需求
- 2.准备工作
- 2.1 订单类
- 2.2 优惠券类
- 3.初版设计
- 4.使用策略模式
- 4.1 策略接口
- 4.2 满减策略
- 4.3 打折策略
- 4.4 策略上下文
- 4.5 订单服务类
- 4.6 服务测试
- 5. 总结
1.需求

这次来模拟一个需求,电商结算金额要使用优惠券,比如有满减券和打折券两类,当然后期会添加,要注意代码的扩展性,这样一来,你会怎么设计呢?
2.准备工作

2.1 订单类
/*** 订单*@author bb 2024/8/22*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {/*** 名称*/private String name;/*** 单价*/private BigDecimal price;/*** 数量*/private BigDecimal count;
}
2.2 优惠券类
/*** 优惠券实体类*@author bb 2024/8/22*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Coupon {private Integer id;/*** 类型 1满减 2打折*/private Integer type;/*** 扣减金额*/private BigDecimal deductionAmount;/*** 门槛*/private BigDecimal threshold;/*** 适用于type=2* 打折额度*/private BigDecimal discountLimit;
}
3.初版设计
看如下代码,两类优惠券通过if连接之后用在同一个方法中,如果有拓展,长期下来就会变的很臃肿,耦合性太强。
/***@author bb 2024/8/22*/
@Slf4j
@Service
public class ToyOrderService {public void paymentOld(Order order, Coupon coupon) {// 总价为数量*单价BigDecimal sum = order.getCount().multiply(order.getPrice());BigDecimal checkout = BigDecimal.ZERO;if (coupon.getType() == 1) {BigDecimal threshold = coupon.getThreshold();BigDecimal deductionAmount = coupon.getDeductionAmount();if (sum.compareTo(threshold) >= 0) {checkout = sum.subtract(deductionAmount);}}if (coupon.getType() == 2) {BigDecimal deductionAmount = coupon.getDiscountLimit();checkout = sum.multiply(deductionAmount);}if (coupon.getType() == 3) {// 不断加if或者改用switch}log.info("order信息:{},优惠券信息:{},最终付款金额为:{}", order, coupon, checkout);}
}
4.使用策略模式

我们可以通过策略模式改造,先来个策略接口,一系列优惠券最终都要得到的是金额,所以这样设计。
4.1 策略接口
/*** 策略接口*@author bb 2024/8/22*/public interface DiscountStrategy {/*** 使用优惠券* @param originalPrice 原价* @return 最终价格*/BigDecimal applyCoupon(BigDecimal originalPrice);
}
4.2 满减策略
/*** 满减优惠券策略* @author bb 2024/8/22*/
//@Component
public class AmountOffDiscount implements DiscountStrategy {private final BigDecimal threshold;private final BigDecimal discount;public AmountOffDiscount(BigDecimal threshold, BigDecimal discount) {this.threshold = threshold;this.discount = discount;}@Overridepublic BigDecimal applyCoupon(BigDecimal originalPrice) {// 达到满减门槛 才扣if (threshold.compareTo(originalPrice) <= 0) {return originalPrice.subtract(discount);}throw new RuntimeException("未达到满减门槛,不予使用!");}
}
4.3 打折策略
/*** 打折优惠券*@author jeff 2024/8/22*/
@NoArgsConstructor
@AllArgsConstructor
public class PercentageDiscount implements DiscountStrategy {private BigDecimal percentage;@Overridepublic BigDecimal applyCoupon(BigDecimal originalPrice) {return originalPrice.multiply(percentage);}
}
4.4 策略上下文
这个类主要是汇总不同策略使用的相同方法。
/***@author bb 2024/8/22*/
@Component
public class ShoppingCartContext {private DiscountStrategy discountStrategy;@Autowired(required = false)public void setDiscountStrategy(DiscountStrategy discountStrategy) {this.discountStrategy = discountStrategy;}public BigDecimal checkout(BigDecimal originalPrice) {if (discountStrategy == null) {throw new RuntimeException("未设置扣减方式,请检查!");}return discountStrategy.applyCoupon(originalPrice);}
}
4.5 订单服务类
通过不同的优惠券类型,我们使用不同的策略。
/***@author bb 2024/8/22*/
@Slf4j
@Service
public class ToyOrderService {@Autowiredprivate ShoppingCartContext shoppingCartContext;public void payment(Order order, Coupon coupon) {// 满减扣款if (coupon.getType() == 1) {AmountOffDiscount discount = new AmountOffDiscount(coupon.getThreshold(), coupon.getDeductionAmount());shoppingCartContext.setDiscountStrategy(discount);}// 打折if (coupon.getType() == 2) {PercentageDiscount percentageDiscount = new PercentageDiscount(coupon.getDiscountLimit());shoppingCartContext.setDiscountStrategy(percentageDiscount);}// 结算金额BigDecimal checkout = shoppingCartContext.checkout(order.getPrice().multiply(order.getCount()));log.info("order信息:{},优惠券信息:{},最终付款金额为:{}", order, coupon, checkout);}}
4.6 服务测试
/***@author bb 2024/8/22*/@SpringBootTest
public class StrategyTest {@Autowiredprivate ToyOrderService toyOrderService;@Testpublic void useFullReduction() {//模拟数据 一般是从db中查到的// 假订单Order order = new Order();order.setCount(new BigDecimal("2"));order.setPrice(new BigDecimal("88"));// 假优惠券Coupon coupon = new Coupon().setType(1).setThreshold(new BigDecimal("50")).setDeductionAmount(new BigDecimal("5"));toyOrderService.payment(order, coupon);}@Testpublic void useDiscounts() {//模拟数据 一般是从db中查到的// 假订单Order order = new Order();order.setCount(BigDecimal.TEN);order.setPrice(new BigDecimal("88"));// 假优惠券Coupon coupon = new Coupon().setType(2).setDiscountLimit(new BigDecimal("8"));toyOrderService.payment(order, coupon);}}
最终结果如下,使用不同的优惠券扣减了不同的金额。


5. 总结

这篇文章并不是简单的梳理了策略模式的使用方式,在使用中我有了更深的体会,在上次我介绍工厂模式再到现在的策略模式,发现两个模式其实很相似,但还是有细微的差别,现在要对此做个总结。
对于工厂模式,其主要目的是通过工厂类或方法来创建对象,而不需要直接在代码中使用new关键字。这有助于隐藏对象的创建细节,并使得代码更加灵活和可维护。然而,工厂模式中的实现类通常需要继承自一个共同的父类或实现一个共同的接口,以保持一定的结构一致性。这种结构化特点在一定程度上限制了参数传递的灵活性,因为它们必须符合共同的接口或继承的父类定义。
相比之下,策略模式中的策略类通常不涉及继承关系,而是通过接口或抽象类来统一管理算法的实现。这使得策略类可以更加灵活地接受不同的参数和配置,以适应不同的算法需求。在策略模式中,你可以根据不同的需求创建不同的策略对象,并通过上下文对象来切换和使用这些策略。这种灵活性使得策略模式在处理算法选择和切换时更加方便和可扩展。
我特意没在上面说出来,我们回过头看AmountOffDiscount,这个类通过的组合的方式将对应的参数放入,而在使用策略的时候shoppingCartContext.setDiscountStrategy()是通过new对象的,这样一来,传参就更为的灵活,而我们回顾工厂模式。
使用工厂设计模式消除多个if-switch,简单易懂,代码解耦!
借助了一个初始化的map,也使得获取方法实现更为便捷,但如果要添加参数,因为父类接口的限制,并不好拓展,所以策略模式的组合方式可拓展性更好。
最后说句实话,我个人觉得工厂模式与策略模式的分界也没有那么清楚,因为有时候想用工厂模式,但也体现出了不同的策略,有时候使用策略模式,但也使用了工厂方法来创建对象,而我们最终的目的还是为了解耦,目的达到即可。
