在 设计模式(一)策略模式 —— 策略模式结构 和 设计模式(二)策略模式 —— 在程序中通过枚举使用策略模式 两篇博文中分析了策略模式的基础使用,在实际的项目开发中要结合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对象和枚举对象
public class CouponDTO {
private CouponTypeEnum couponType;
// other field
public CouponTypeEnum getCouponType() {
return couponType;
}
public void setCouponType(CouponTypeEnum couponType) {
this.couponType = couponType;
}
}
public enum CouponTypeEnum {
DISCOUNT("折扣券"),
REBATE("抵扣券"),
FULL_REDUCE("满减券");
private String name;
CouponTypeEnum(String name) {
this.name = name;
}
}
定义策略接口
策略接口中包含两个方法的定义。
- 定义当前策略类的适用的优惠券类型
- 定义具体的计算策略交给子类实现。
public interface CouponStrategy {
CouponTypeEnum applyCouponType();
Object couponHandler(CouponDTO couponDTO);
}
定义优惠券策略计算的上下文对象
策略上下文对象主要完成两项操作
- 应用启动时从spring容器中获取所有策略接口的实现类,注册到优惠券策略集合中
- 提供对外暴露的优惠券计算接口,通过优惠券类型从优惠券策略集合中获取适用的策略类,委派具体的策略类完成计算逻辑。
@Component
public class CouponStrategyContext implements ApplicationContextAware {
private static Map couponStrategyMap;
public static Object couponHandler(CouponDTO couponDTO) {
return getCouponStrategy(couponDTO.getCouponType()).couponHandler(couponDTO);
}
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())));
}
@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容器中
@Service
public class DiscountCouponStrategy implements CouponStrategy {
@Override
public CouponTypeEnum applyCouponType() {
return CouponTypeEnum.DISCOUNT;
}
@Override
public Object couponHandler(CouponDTO couponDTO) {
System.out.println("执行折扣券策略");
return null;
}
}
@Service
public class FullReduceCouponStrategy implements CouponStrategy {
@Override
public CouponTypeEnum applyCouponType() {
return CouponTypeEnum.FULL_REDUCE;
}
@Override
public Object couponHandler(CouponDTO couponDTO) {
System.out.println("执行满减券策略");
return null;
}
}
@Service
public class RebateCouponStrategy implements CouponStrategy {
@Override
public CouponTypeEnum applyCouponType() {
return CouponTypeEnum.REBATE;
}
@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的特性,简化了手动创建策略类实例的过程。
- 调用方无需知道具体的策略类,降低模块间的耦合。
- 方便扩展,只需要新增或者修改某一具体策略类即可,完全符合开闭原则。
- 暂时没找到缺点...



