策略模式实践

1,970 阅读5分钟

🗺️背景

  物流行业的新规后,消费者在购物平台购买商品后,收货信息保存在购物平台,一般不会交给发货方,当仓库需要发货时要通过购物平台对接不同的快递公司完成发货

  本来的需求只有一个来源平台,后续有了不同的客户和不同的来源平台,我不想在原来的人的下面叠if else,所以使用了策略模式进行修改,后续再有不同的来源平台,只要增加实现类就可以了

👓正文

  使用策略模式配合工厂模式 来解决上面的问题

1540606553197.jpg

✏️定义策略接口

  定义一个策略接口 其中包含了所有策略都要实现的方法

/**
 * @Title ILogisticsWaybill
 * @Description 电子面单相关对接的策略接口
 * @Version 1.0
 */
public interface ILogisticsWaybill {
    /**
     * 获取对应电子面单平台的名字
     * @return  例如: 抖音 快手
     */
    String getWaybillName();

    /**
     * 是否使用这个实现
     * @param customerOrderSource   客户订单来源
     * @return  是否
     */
    boolean useFlag(String customerOrderSource);

    /**
     * 一些公用的方法
     */
    void common();

    /**
     * 订单受理时根据订单信息 向平台下单创建电子面单
     * @param o 订单信息
     * @return  快递单信息
     */
    LogisticsCreateOrderResDto createOrder(LogisticsCreateOrderDto o);

    /**
     * 取消 快递单号
     * @param cancelDto 取消单号等信息
     * @return 成功与否
     */
    boolean cancelOrder(WaybillCancelDto cancelDto);

}

🖋️实现具体策略

1.定义一个抽象实现类

  定义一个抽象的实现类,包含了启动初始化 和 一个公用的方法

/**
 * @Title BaseLogisticsWaybill
 * @Description 共用的算法逻辑抽象类
 * @Version 1.0
 */
public abstract class BaseLogisticsWaybill implements InitializingBean,ILogisticsWaybill{

    private Logger logger = LoggerFactory.getLogger(BaseLogisticsWaybill.class);

    @Override
    public void afterPropertiesSet() {
        logger.info("初始化" + this.getWaybillName() + "策略的实现类");
        LogisticWaybillFactory.registerStrategy(this.getWaybillName(),this);
    }

    @Override
    public void common() {
        // 公用方法 在不同策略执行完后 共同的操作  比如
        logger.info("记录日志");
        logger.info("推送通知");
    }
}

2.定义具体实现类

  • 菜鸟
/**
 * @Title CaiNiaoWaybillImpl
 * @Description 菜鸟电子面单相关接口实现类
 * @Version 1.0
 */
@Slf4j
@Component
public class CaiNiaoWaybillImpl extends BaseLogisticsWaybill {

    @Override
    public String getWaybillName() {
        return WaybillNameEnum.CAI_NIAO.getCode();
    }

    /**
     * 直接返回false  不参与循环判断
     * @param customerOrderSource 客户订单来源
     * @return false
     */
    @Override
    public boolean useFlag(String customerOrderSource) {
        return false;
    }

    @Override
    public LogisticsCreateOrderResDto createOrder(LogisticsCreateOrderDto o) {
        log.info("通过 菜鸟 下单");
        return new LogisticsCreateOrderResDto("菜鸟");
    }

    /**
     * 菜鸟取消快递单号
     */
    @Override
    public boolean cancelOrder(WaybillCancelDto cancelDto) {
        return true;
    }
}
  • 抖音
/**
 * @Title DouYinOrderUtils
 * @Description 抖音订单电子面单
 * @Version 1.0
 */
@Slf4j
@Component
public class DouYinWaybillImpl extends BaseLogisticsWaybill {

    @Override
    public String getWaybillName() {
        return WaybillNameEnum.DOU_YIN.getCode();
    }

    @Override
    public boolean useFlag(String customerOrderSource) {
        return "DOUYIN".equals(customerOrderSource) || "DYXD".equals(customerOrderSource) || "douyin".equals(customerOrderSource);
    }

    @Override
    public LogisticsCreateOrderResDto createOrder(LogisticsCreateOrderDto o) {
        log.info("通过 抖音 下单");
        return new LogisticsCreateOrderResDto("抖音");
    }

    @Override
    public boolean cancelOrder(WaybillCancelDto cancelDto) {
        return false;
    }
}
  • 拼多多
/**
 * @Title PddWaybillImpl
 * @Description 拼多多电子面单接口实现类
 * @Version 1.0
 */
@Slf4j
@Component
public class PddWaybillImpl extends BaseLogisticsWaybill {
    @Override
    public String getWaybillName() {
        return WaybillNameEnum.PDD.getCode();
    }

    /**
     * 是否使用此实现类
     * @param customerOrderSource   客户订单来源
     * @return                      是否
     */
    @Override
    public boolean useFlag(String customerOrderSource) {
        return "pdd".equals(customerOrderSource);
    }

    @Override
    public LogisticsCreateOrderResDto createOrder(LogisticsCreateOrderDto o) {
         log.info("通过 拼多多 下单");
        return new LogisticsCreateOrderResDto("拼多多");
    }

    @Override
    public boolean cancelOrder(WaybillCancelDto cancelDto) {
        return false;
    }
}

  实际对接有很多不同的平台,传入的参数也比较复杂,先做个简单示例.

3.枚举 和 一些domain

/**
 * @Title WaybillNameEnum
 * @Description 电子面单平台枚举
 * @Version 1.0
 */
@Getter
public enum WaybillNameEnum {
    /**
     * 对接的电子面单平台
     */
    CAI_NIAO("cainiao","菜鸟"),
    DOU_YIN("douyin","抖音"),
    PDD("pdd","拼多多"),
    ;
    
    private final String code;
    private final String name;

    WaybillNameEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }
}
/**
 * @Title LogisticsUpdateOrder
 * @Description 创建电子面单接口的 dto
 * @Version 1.0
 */
@Data
public class LogisticsCreateOrderDto {
    /**
     * .....省略
     */
}
/**
 * @Title LogisticsUpdateOrder
 * @Description 创建电子面单 接口的返回值
 * @Version 1.0
 */
@Data
@AllArgsConstructor
public class LogisticsCreateOrderResDto {

    private String remark;
    /**
     * 省略.......
     */
}
/**
 * @Title WaybillCancelDto
 * @Description 电子面单 取消对象
 * @Version 1.0
 */
@Data
public class WaybillCancelDto {
    /**
     * 省略.......
     */
}

🏭︎定义一个工厂类

  主要是定义一个map,在项目启动的时候把各个实现类保存到map中,然后在调用的时候根据customerOrderSource客户订单来源字段来获取对应的实现类

/**
 * @Title LogisticWaybillFactory
 * @Description 电子面单接口实现类工厂
 * @Version 1.0
 */
@Slf4j
@Component
public class LogisticWaybillFactory {

    /**
     * 保存 策略实现类 <接口名,具体策略实现类>
     */
    private final static Map<String, BaseLogisticsWaybill> STRATEGY_MAP = new ConcurrentHashMap<>(16);

    /**
     * 添加策略实例
     */
    public static void registerStrategy(String type, BaseLogisticsWaybill strategy) {
        STRATEGY_MAP.put(type, strategy);
    }

    /**
     * 获取策略实例
     * 根据实现类里的 useFlag 判断当前实现类是否使用  如果都不用 就用菜鸟的
     * @param customerOrderSource 客户订单来源   有时候即使是同一个平台 也会有多个不同的字符串
     * @return  实现类实例
     */
    public static BaseLogisticsWaybill getImpl(String customerOrderSource) {
        for (BaseLogisticsWaybill value : STRATEGY_MAP.values()) {
            if (value.useFlag(customerOrderSource)){
                return value;
            }
        }
        return STRATEGY_MAP.get(WaybillNameEnum.CAI_NIAO.getCode());
    }

    /**
     * 根据map的key来获取实现类   有的客户固定一个平台 用这个
     * @param mapKey    map的key
     * @return          实现类实例
     */
    public static BaseLogisticsWaybill getImplByKey(WaybillNameEnum mapKey) {
        return STRATEGY_MAP.get(mapKey.getCode());
    }

}

🧑‍🏭定义一个策略执行类

/**
 * @Title LogisticWaybillExec
 * @Description 电子面单接口执行类
 * @Version 1.0
 */
@Service
public class LogisticWaybillExec {
    /**
     * 创建电子面单
     * @param customerOrderSource   客户订单来源
     * @param dto                   订单信息
     * @return                      电子面单信息
     */
    public LogisticsCreateOrderResDto createOrder(String customerOrderSource, LogisticsCreateOrderDto dto){
        BaseLogisticsWaybill impl = LogisticWaybillFactory.getImpl(customerOrderSource);
        LogisticsCreateOrderResDto order = impl.createOrder(dto);
        // 通用方法
        impl.common();
        return order;
    }

    /**
     * 取消电子面单
     * @param customerOrderSource   客户订单来源
     * @param cancelDto             取消的对象
     * @return                      取消成功与否
     */
    public boolean cancelOrder(String customerOrderSource,WaybillCancelDto cancelDto) {
        BaseLogisticsWaybill impl = LogisticWaybillFactory.getImpl(customerOrderSource);
        return impl.cancelOrder(cancelDto);
    }

}

🎣接口调用实现

/**
 * @Title StrategyController
 * @Description 接口
 * @Version 1.0
 */
@RestController
@RequestMapping("/execute")
public class StrategyController {

    @Autowired
    LogisticWaybillExec logisticWaybillExec;

    @GetMapping("/{strategyName}")
    public String executeStrategy(@PathVariable String strategyName) {
        LogisticsCreateOrderResDto order = logisticWaybillExec.createOrder(strategyName, new LogisticsCreateOrderDto());
        return "来源: " + order.getRemark();
    }
}

  可以看到在项目启动时,会初始化各个实现类

img00.png

1.传入参数 DYXD

img1.png

可以看到根据传入的参数调用了对应的实现类,并且触发了公用的方法

img2.png

2.传入参数 pdd

img3.png

img4.png

3.传入任意参数

  这次的示例里菜鸟是兜底的,传入的参数找不到对应的实现类时,默认使用菜鸟的实现类,以实际业务情况为准

img5.png

img6.png

🔚总结

  以上就是我的这次策略模式实践,结合策略模式和工厂模式,减少if else的叠加,简化后续维护😁

1540606553717.jpg

示例代码git地址