项目背景
电商中的营销服务,结算优惠卷价格
需求描述
营销服务:根据用户选择不同的优惠卷实现不同的计算方式;
其他的应用场景:
1、系统有很多类,而他们的区别仅仅在于他们的行为不同;
2、一个系统需要动态的在几种实现方式里选择一种
难点分析
设计优惠卷表结构,和优惠类型表
在第二张的优惠卷类型表中,我给了一个枚举值的字段,枚举值是为了配合引用上下文。策略接口说白了也就是一个接口多个实现类(个人感想) ,而我是创建一个引用上下文的类通过枚举值传参来获取到那个实现类的接口对象。(具体往下看)
怎么定义接口
在本人的项目需求上,我是定义了一个接口,接口只有一个方法,而这个接口有两个实现类。也就是两个计算优惠卷的方法:1、根据满减类型计算(满100减20) 2、根据折扣来进行计算(满100打八折)
//策略方法
public interface CouponStrategy {
//计算方法
BigDecimal doActive(ComputeReq computeReq);
}
//折扣优惠卷计算类
@Component
public class DiscountCoupon implements CouponStrategy {
@Override
public BigDecimal doActive(ComputeReq req) {
//获取订单原价格跟满足条件价格作比较
int i = req.getOrderPrice ().compareTo(req.getMinOrderAmount());
if (i<0){
//如果不满足条件就只接返回原价格
return req.getOrderPrice ();
}
//在数据库折扣比率我存的数据是小于10的大于0.1的小数,在这了×0.1转换为0.00,通过百分比计算
BigDecimal multiply = req.getOrderPrice ().multiply(new BigDecimal("0.1").multiply(req.getDiscountPerentage()));
return multiply;
}
}
//满减优惠卷计算类
@Component
public class FullMinusCoupon implements CouponStrategy {
@Override
public BigDecimal doActive(ComputeReq req) {
//获取订单原价格跟满足条件价格作比较
int i = req.getOrderPrice ().compareTo(req.getMinOrderAmount());
if (i<0){
//如果不满足条件就只接返回原价格
return req.getOrderPrice ();
}
//满减金额相减价格返回
return req.getOrderPrice ().subtract(req.getDiscountAmount());
}
}
在上面中两个实现类都加上了@Component注解,让项目启动时将两个类加载到bean容器里。
引用上下文类
@Component
public class CouponStrategyContext {
@Autowired
private Map<String, CouponStrategy> couponStrategyMap =new HashMap<>();
public CouponStrategy getType(CouponEnum couponEnum){
return couponStrategyMap.get (couponEnum.getCouponName ());
}
}
在这个类中,也是通过@Component注解注入到bean容器里,用@Autowired注入一个HashMap集合的成员变量。而@Autowired注解有着自动装配工作的功能,在项目启动时也是将已经放入bean容器里的两个实现类存入map中,然后再提供方法根据枚举参数(实例名)获取到具体的对象实例。
枚举类
@Getter
@AllArgsConstructor
public enum CouponEnum {
/**
* 满减类型
*/
FULLMINUS(1,"fullMinusCoupon"),
/**
* 折扣
*/
DISCOUNT(2,"discountCoupon");
/**
* 优惠卷类型和名称
*/
private final Integer couponCode;
private final String couponName;
}
请求参数类
@Data
public class ComputeReq {
/** 优惠卷类型*/
private Integer couponTypeId;
/** 满减金额 */
private BigDecimal discountAmount;
/** 折扣百分比(折扣类型) */
private BigDecimal discountPerentage;
/** 所需最低金额(满减类型) */
private BigDecimal minOrderAmount;
/** 订单价格(原价格) */
private BigDecimal orderPrice;
}
请求测试
controller层
@RestController
@RequestMapping("/userRepertory")
public class TUserRepertoryController {
@Autowired
private ITUserRepertoryService itUserRepertoryService;
@PostMapping("/computeCoupon")
public Result computeCoupon(@RequestBody ComputeReq req){
return itUserRepertoryService.computePrice(req);
}
}
//接口层
public interface ITUserRepertoryService {
Result computePrice(ComputeReq req);
}
通过controller层接收参数,由于控制层不做业务逻辑,我们直接传给service层去实现业务逻辑使用策略接口;
Service实现层
@Service
public class ITUserRepertoryServiceImpl implements ITUserRepertoryService {
@Autowired
private TCouponTypeMapper tCouponTypeMapper;
@Override
public Result computePrice(ComputeReq req) {
//根据优惠类型id查找到类型表里的数据
TCouponType tCouponType = tCouponTypeMapper.selectTCouponTypeByCouponTypeId (req.getCouponTypeId ().longValue ());
//查找出类型数据中的枚举值,通过枚举值转换为枚举对象获取引用上下文的接口对象
CouponStrategy couponStrategy = couponStrategyContext.getType (Enum.valueOf (CouponEnum.class, tCouponType.getEnumValue ()));
//获取的接口对象执行方法
BigDecimal bigDecimal = couponStrategy.doActive (req);
if (bigDecimal.compareTo (req.getOrderPrice ())==0){
return Result.error (req.getOrderPrice (),"价格不满足优惠条件");
}
return Result.success (bigDecimal,"优惠后的价格为"+bigDecimal);
}
}
调试结果
满减卷测试
折扣卷测试
不满足条件测试
总结:
策略模式遵循以下几个原则:
-
单一职责原则 (Single Responsibility Principle, SRP): 每个策略类应该只负责一个具体的算法实现。这样可以确保每个类的职责是清晰明确的。
-
开闭原则 (Open-Closed Principle, OCP): 策略模式通过将算法封装在独立的策略类中,使得可以在不修改现有代码的情况下轻松地添加新的策略。对新增策略的支持会更加灵活和可扩展。
-
接口隔离原则 (Interface Segregation Principle, ISP): 策略模式基于接口或抽象类定义策略,通过接口的方式来实现多态。每个具体的策略都应该实现相应的接口,避免实现了无关的接口方法。
-
依赖倒置原则 (Dependency Inversion Principle, DIP): 策略模式使用依赖注入的方式将策略与调用方解耦。调用方只需要依赖于策略接口或抽象类,而不是具体的策略实现。
个人小结:
本人采用枚举值来辅助引用上下文类获取接口的实例,本人代码实现遵循开闭原则的显点就在于:如果我再增加一个优惠算法,比如代金卷、各种门槛卷等,我只需要在数据库里的优惠卷类型表添加相应的数据。在项目代码中,只需在枚举类里添加枚举属性,再添加一个算法的实现类即可。