前言
在 设计模式(一)策略模式 —— 策略模式结构 和 设计模式(二)策略模式 —— 在程序中通过枚举使用策略模式 两篇博文中分析了策略模式的基础使用,在实际的项目开发中要结合spring容器使用策略模式,这篇博文以电商优惠券处理为例演示在spring中优雅的使用策略模式。
场景分析
比如在电商系统中,对各种类型的优惠券优惠金额的算法处理。如果直接写代码可能就出现了下面这段代码一样
public Object couponTypeHandler(String couponType){
if ("抵扣券".equals(couponType)){
// doSomething
}else if ("满减券".equals(couponType)){
// doSomething
}else if ("折扣券".equals(couponType)){
// doSomething
}else {
// doSomething
}
return new Object();
}
如果优惠券类型越来越多,这里的if...else或者switch的判断条件也会越来越多,后续对这个方法的维护也会越来越困难,也不符合开闭原则。
在spring中优雅的使用策略模式
定义优惠券相关基础类
定义Demo中使用的DTO对象和枚举对象
/**
* 优惠券计算传输对象
*
* @author lishuzhen
* @createTime 2022年05月29日 22:23:00
*/
public class CouponDTO {
private CouponTypeEnum couponType;
// other field
public CouponTypeEnum getCouponType() {
return couponType;
}
public void setCouponType(CouponTypeEnum couponType) {
this.couponType = couponType;
}
}
/**
* 优惠券类型枚举类
*
* @author lishuzhen
* @createTime 2022年05月29日 22:28:35
*/
public enum CouponTypeEnum {
DISCOUNT("折扣券"),
REBATE("抵扣券"),
FULL_REDUCE("满减券");
private String name;
CouponTypeEnum(String name) {
this.name = name;
}
}
定义策略接口
策略接口中包含两个方法的定义。
- 定义当前策略类的适用的优惠券类型
- 定义具体的计算策略交给子类实现。
/**
* 优惠券策略接口
*
* @author lishuzhen
* @createTime 2022年05月29日 22:48:00
*/
public interface CouponStrategy {
/**
* 适用的优惠券类型
*
* @return
*/
CouponTypeEnum applyCouponType();
/**
* 优惠券策略算法执行入口
*
* @param couponDTO
* @return
*/
Object couponHandler(CouponDTO couponDTO);
}
定义优惠券策略计算的上下文对象
策略上下文对象主要完成两项操作
- 应用启动时从spring容器中获取所有策略接口的实现类,注册到优惠券策略集合中
- 提供对外暴露的优惠券计算接口,通过优惠券类型从优惠券策略集合中获取适用的策略类,委派具体的策略类完成计算逻辑。
/**
* 优惠券策略上下文对象
*
* @author lishuzhen
* @createTime 2022年05月29日 22:58:00
*/
@Component
public class CouponStrategyContext implements ApplicationContextAware {
/**
* 优惠券策略类集合
*/
private static Map couponStrategyMap;
/**
* 策略上下文对象委派具体的策略执行算法
*
* @param couponDTO
* @return
*/
public static Object couponHandler(CouponDTO couponDTO) {
return getCouponStrategy(couponDTO.getCouponType()).couponHandler(couponDTO);
}
/**
* 获取适用的策略处理类
*
* @param couponType
* @return
*/
private static CouponStrategy getCouponStrategy(CouponTypeEnum couponType) {
return Optional.ofNullable(couponStrategyMap.get(couponType.name()))
.orElseThrow(() -> new RuntimeException(String.format("not found coupon type strategy , coupon type is %s", couponType.name())));
}
/**
* 从容器中加载所有优惠券策略接口的实现类,注册到优惠券策略集合中
*
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map strategyBeans = applicationContext.getBeansOfType(CouponStrategy.class);
if (strategyBeans == null || strategyBeans.size() == 0) {
// 无可用的优惠券策略
return;
}
couponStrategyMap = new HashMap<>(strategyBeans.size());
for (CouponStrategy strategy : strategyBeans.values()) {
couponStrategyMap.put(strategy.applyCouponType().name(), strategy);
System.out.println("register strategy " + strategy.applyCouponType());
}
}
}
定义具体的策略实现类
策略实现类要完成三个操作
- 实现applyCouponType方法,表明当前策略类适用的优惠券类型。供策略上下文对象中注册策略集合使用。
- 实现couponHandler方法,完成自己的主要职责,对优惠券进行计算
- 将自己注册到spring容器中
/**
* 折扣券策略
*
* @author lishuzhen
* @createTime 2022年05月29日 23:01:00
*/
@Service
public class DiscountCouponStrategy implements CouponStrategy {
/**
* 适用的优惠券类型
*
* @return
*/
@Override
public CouponTypeEnum applyCouponType() {
return CouponTypeEnum.DISCOUNT;
}
/**
* 优惠券策略算法执行入口
*
* @param couponDTO
* @return
*/
@Override
public Object couponHandler(CouponDTO couponDTO) {
System.out.println("执行折扣券策略");
return null;
}
}
/**
* 满减券计算策略
*
* @author lishuzhen
* @createTime 2022年05月29日 23:01:55
*/
@Service
public class FullReduceCouponStrategy implements CouponStrategy {
/**
* 适用的优惠券类型
*
* @return
*/
@Override
public CouponTypeEnum applyCouponType() {
return CouponTypeEnum.FULL_REDUCE;
}
/**
* 优惠券策略算法执行入口
*
* @param couponDTO
* @return
*/
@Override
public Object couponHandler(CouponDTO couponDTO) {
System.out.println("执行满减券策略");
return null;
}
}
/**
* 抵扣券计算策略
*
* @author lishuzhen
* @createTime 2022年05月29日 23:02:40
*/
@Service
public class RebateCouponStrategy implements CouponStrategy {
/**
* 适用的优惠券类型
*
* @return
*/
@Override
public CouponTypeEnum applyCouponType() {
return CouponTypeEnum.REBATE;
}
/**
* 优惠券策略算法执行入口
*
* @param couponDTO
* @return
*/
@Override
public Object couponHandler(CouponDTO couponDTO) {
System.out.println("执行抵扣券策略");
return null;
}
}
调用方代码
调用只需要调用策略上下文对象即可。
// 模拟业务参数中的优惠券类型
CouponDTO couponDTO = new CouponDTO();
couponDTO.setCouponType(CouponTypeEnum.REBATE);
// 委派优惠券策略上下文对象对优惠券进行计算
CouponStrategyContext.couponHandler(couponDTO);
启动应用
注册优惠券策略日志
日志中可以看到,在CouponStrategyContext的setApplicationContext方法中打印的注册日志已经打印到控制台,说明注册优惠券策略集合已完成。
23:51:36.505 [restartedMain] INFO o.a.c.c.StandardEngine - [log,173] - Starting Servlet engine: [Apache Tomcat/9.0.41]
23:51:36.584 [restartedMain] INFO o.a.c.c.C.[.[.[/] - [log,173] - Initializing Spring embedded WebApplicationContext
register strategy DISCOUNT
register strategy FULL_REDUCE
register strategy REBATE
调用策略上下文对象,模拟业务场景
在模拟调用方的代码中写了CouponTypeEnum.REBATE类型,控制台正常打印了“执行抵扣券策略”,说明已经匹配到了具体的策略类。
23:55:06.333 [http-nio-8080-exec-1] INFO o.a.c.c.C.[.[.[/] - [log,173] - Initializing Spring DispatcherServlet 'dispatcherServlet'
执行抵扣券策略
总结
优点
- 利用Spring容器IOC和DI的特性,简化了手动创建策略类实例的过程。
- 调用方无需知道具体的策略类,降低模块间的耦合。
- 方便扩展,只需要新增或者修改某一具体策略类即可,完全符合开闭原则。
缺点
- 暂时没找到缺点...