项目重构:设计模式(策略模式)

策略模式(Strategy Pattern)属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
其主要目的是通过定义相似的算法,替换if else 语句写法,并且可以随时相互替换。

本文参考文章 设计模式最佳套路—— 愉快地使用策略模式

由于项目中,有个需求,通过计费引擎对不同类型进行计费,但是每个类型的计费参数又不一样,一开始通过if-else解决

if (typeA) {
    逻辑1
} else if (typeB) {
    逻辑2
} else if (typeC) {
    逻辑3
} else {
    逻辑4
}

虽然问题解决了,但是每次我们添加一个类型就要修改这段代码,而且代码也不美观;所以,对代码进行了重构
在spring框架中,我们可以通过getBean获取到Spring容器中已初始化的bean,如果我们实现了一个计算参数的接口,然后获取到不同的类型其实现类,这样就可以避免我们增加一个类型就要修改if-else分支

流程图

image.png

获取计费接口

public interface FeeChargingParamsHandler {

    /**
     * FeeChargingParamsHandler :: getChargingType
     * <p>TO:获得计费类型
     * <p>DO:确定一个唯一性的计费参数方法类型,相当于beanName
     * <p>HISTORY: 2020/11/28 liuha : Created.
     *
     * @return String 计费数据实现类型
     */
    String getChargingType();

    /**
     * FeeChargingParamsHandler :: getChargingData
     * <p>TO:获取记录计费数据
     * <p>DO:返回ChargingDataVO格式数据
     * <p>HISTORY: 2020/11/28 liuha : Created.
     *
     * @param originalRecordId 计费记录id
     * @return ChargingDataVO  计费数据
     */
    ChargingDataVO getChargingData(String originalRecordId);
}

返回参数的类

@Data
public class ChargingDataVO {

    /**
     * 参数的名称
     */
    private String name;
    /**
     * 参数类型
     */
    private String type;
    /**
     * 参数值
     */
    private List<List<Map>> value;


    /**
     * ChargingDataVO:: addMApValue
     * <p>TO:添加value值
     * <p>DO:初始化value,添加值到value
     * <p>HISTORY: 2020/11/28 liuha : Created.
     *
     * @param mapList 变量
     */
    public void addMApValue(List<Map> mapList) {
        //初始化数据
        if (this.value == null) {
            this.value = new ArrayList<>();
        }
        this.value.add(mapList);
    }


}

这里是由于业务的要求,参数值一个二维码数组,这里避免掉业务会乱传入数据,所以才会使用了 List<List<Map> 的一个数据结构

建立简单工厂

@Component
public class FeeChargingHandlerFactory implements InitializingBean, ApplicationContextAware {

    private static final
    Map<String, FeeChargingParamsHandler> FEE_CHARGING_HANDLER_MAP = new HashMap(8);

    private ApplicationContext appContext;


    /**
     *  FeeChargingHandlerFactory ::
     *  <p>TO:传入类型获取对应的处理器
     *  <p>HISTORY: 2020/11/28 liuha : Created.
     *  @param    chargingType  计费类型
     *  @return   返回类型对应的处理器
     */
    public FeeChargingParamsHandler getHandler(String chargingType) {
        return FEE_CHARGING_HANDLER_MAP.get(chargingType);
    }

    @Override
    public void afterPropertiesSet() {
        // 将 Spring 容器中所有的 FeeChargingParamsHandler 注册到 FEE_CHARGING_HANDLER_MAP
        appContext.getBeansOfType(FeeChargingParamsHandler.class)
                .values()
                .forEach(handler -> FEE_CHARGING_HANDLER_MAP.put(handler.getChargingType(), handler));
    }



    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        appContext = applicationContext;
    }
}

实现计费参数接口

@Component
public class TestFeeChargingParamsHandler implements FeeChargingParamsHandler {
    /**
     * TestFeeChargingParamsHandler:: getChargingType
     * <p>TO:获得实现方法的计费数据实现类型
     * <p>DO:确定一个唯一性的计费参数方法类型,相当于beanName
     * <p>HISTORY: 2020/11/28 liuha : Created.
     *
     * @return String 计费数据实现类型
     */
    @Override
    public String getChargingType() {
        return "test";
    }

      /**
     * TestFeeChargingParamsHandler:: getChargingData
     * <p>TO:获取记录计费数据
     * <p>DO:返回ChargingDataVO格式数据
     * <p>HISTORY: 2020/11/28 liuha : Created.
     *
     * @param originalRecordId 计费记录id
     * @return ChargingDataVO  计费数据
     */
    @Override
    public ChargingDataVO getChargingData(String originalRecordId) {
        ChargingDataVO chargingDataVO = new ChargingDataVO();
        chargingDataVO.setName("管道");
        chargingDataVO.setType("String");
        List<Map> mapList = new ArrayList<>();
        Map<String, String> map = new HashMap<>();
        map.put("name", "铺设方式");
        map.put("value", "架空");
        mapList.add(map);
        Map<String, String> map1 = new HashMap<>();
        map1.put("name", "压力管道级别");
        map1.put("value", "GC3");
        mapList.add(map1);
        Map<String, String> map2 = new HashMap<>();
        map2.put("name", "管道外径");
        map2.put("value", "60");
        mapList.add(map2);
        Map<String, String> map3 = new HashMap<>();
        map3.put("name", "管道长度]);
        map3.put("value", "1"); 
        mapList.add(map3);
        chargingDataVO.addMApValue(mapList);
        return chargingDataVO;
    }
}

代码调用

在业务中中,我们通过 FeeChargingHandlerFactory 来获取类型实现的计费接口实现类,从而获取到计费的参数:


 @Autowired
 private FeeChargingHandlerFactory handlerFactory;

  ......... 业务代码

 /**
     * ITjFeeChargingServiceImpl:: getChargingData
     * <p>TO:获取业务实现计费参数数据
     * <p>DO:从handlerFactory获取到FeeChargingParamsHandler;调用实现类;返回结果
     * <p>HISTORY: 2020/11/29 liuha : Created.
     *
     * @param recordId 计费的业务数据id
     * @param type     实现类的type类型
     * @return ChargingParamData  类型的计费参数
     */
  private ChargingParamData getChargingData(String recordId, String type) {
        //查找策略实现
        FeeChargingParamsHandler handler = handlerFactory.getHandler(type);
        if (handler == null) {
            throw new JeecgBootException("未找到实现二维表取的实现类");
        }
        return handler.getChargingData(recordId);
    }


这样便可以解决了多层if-else的代码了,如果添加了一个类型,只需要添加一个新的策略实现即可,其实还有一个问题,由于工厂模式中,我们使用了map存入实现类,

@Override
    public void afterPropertiesSet() {
        // 将 Spring 容器中所有的 FeeChargingTableParamsHandler 注册到 FEE_CHARGING_HANDLER_MAP
        appContext.getBeansOfType(FeeChargingTableParamsHandler.class)
                .values()
                .forEach(handler -> FEE_CHARGING_HANDLER_MAP.put(handler.getChargingType(), handler));
    }

如果存在一样的类型参数,就会把之前存入的策略实现,替换掉,所以可以在遍历过程中判断是否存在重复的type,由于我是,内部方法调用,可以不做校验。另外,如果类型可以确定,使用switch-case的方法也实现分支,策略模式在项目还可以和模板方法结合使用,这样就可以解决主体方法一样,但是部分代码存在分支的情况,比如业务实现工作

最近的文章

项目重构:设计模式(模板方法模式)

在模板模式(TemplatePattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。最近,在项目中,需要调用用户提供的接口发送微信模板信息,然后提供了信息模板{"Personnel…

继续阅读
更早的文章

个人代码规范

由于之前和外包团队进行合作。在通用功能的调用,发现不同团队的编写水平不一致,导致通用功能很多地方都需要重构;所以自己在重构代码的同时,也整理自己写代码的习惯1.一个方法只做一件事或者一个方向的事情赋值set;保存save;判断is;转换to1)获取单个对象的方法用get做前缀。2)获取多个对象的方法…

继续阅读