设计模式(三)策略模式——在Spring中使用策略模式

前言

在 设计模式(一)策略模式 —— 策略模式结构 和 设计模式(二)策略模式 —— 在程序中通过枚举使用策略模式 两篇博文中分析了策略模式的基础使用,在实际的项目开发中要结合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的特性,简化了手动创建策略类实例的过程。
  • 调用方无需知道具体的策略类,降低模块间的耦合。
  • 方便扩展,只需要新增或者修改某一具体策略类即可,完全符合开闭原则。

缺点

  • 暂时没找到缺点...

你可能感兴趣的:(设计模式,策略模式,spring,java,后端)