likes
comments
collection
share

利用策略模式结合Springboot框架

作者站长头像
站长
· 阅读数 12

利用策略模式解决多条件问题

问题重现

这是公司代码里面的一个接口,我需要根据type的不同,去决定要不要存储里面的对象。

ini复制代码 @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveDimensionsByQuestionBankId(List<MbDimensionsDto> dimensions, Long questionBankId,Integer type) {
        //如果是无维度,说明不用保存
        if(type == QuestionBankSettingTypeEnum.无维度.getValue()){
            return true;
        }
        //如果不是,就先检验
        checkIsDimensionsDtoLegal(dimensions);


        if(type.equals(QuestionBankSettingTypeEnum.一极维度.getValue())){
            dimensions.forEach(mbDimensionDto -> {
                MbDimension mbDimension = new MbDimension();
                BeanUtils.copyProperties(mbDimensionDto, mbDimension);
                mbDimension.setQuestionBankId(questionBankId);
                this.save(mbDimension);
            });
            return true;
        }

        if(type.equals(QuestionBankSettingTypeEnum.二级维度.getValue())){
            //新增树状dimensions
            dimensions.forEach(mbDimensionDto -> {
                MbDimension mbDimension = new MbDimension();
                BeanUtils.copyProperties(mbDimensionDto, mbDimension);
                mbDimension.setQuestionBankId(questionBankId);
                this.save(mbDimension);
                Long fatherId = mbDimension.getId();
                List<MbDimensionsDto> children = mbDimensionDto.getChildren();

                if(children == null){
                    return;
                }
                children.forEach(son -> {
                    MbDimension mbDimensionSon = new MbDimension();
                    BeanUtils.copyProperties(son, mbDimensionSon);
                    mbDimensionSon.setQuestionBankId(questionBankId);
                    //新增后的dimension的id,即为chidren dimension的父亲id.
                    mbDimensionSon.setParentId(fatherId);
                    this.save(mbDimensionSon);
                });
            });
            return true;
        }
        return false;
    }

不得不说,相当的丑陋!!

有一个学长的话在我脑子中不断回响:“写代码就得优雅!!”

于是我重拾起他说了很多次的策略模式。

实战

定义策略接口和实现类

对于保存维度这个方法,我们不妨给他设置一个接口。

php复制代码/**
 * @author ht
 */
public interface SaveDimensionStrategy {
    /**
     * 当前的维度模式,是无维度还是一级维度,还是二级维度
     * @return
     */
    Integer getType();

    /**
     * 根据当前维度,进行处理
     * @param dimensions
     * @param questionBankId
     * @return
     */
    boolean saveDimensionStrategy(List<MbDimensionsDto> dimensions, Long questionBankId);
}

我们目前有三个类,都需要实现这个接口,来进行不同的行为,在这里就是对维度的不同操作。

无维度

由于无维度无需处理,直接返回true即可

typescript复制代码@Component
public class SaveNoDimensionStrategy implements SaveDimensionStrategy {

    @Override
    public Integer getType() {
        return QuestionBankSettingTypeEnum.无维度.getValue();
    }

    @Override
    public boolean saveDimensionStrategy(List<MbDimensionsDto> dimensions, Long questionBankId) {
        return true;
    }
}

一级维度

typescript复制代码@Component
public class SaveOneDimensionStrategy implements SaveDimensionStrategy {

    @Resource
    private MbDimensionService mbDimensionService;

    @Override
    public Integer getType() {
        return QuestionBankSettingTypeEnum.一极维度.getValue();
    }

    @Override
    public boolean saveDimensionStrategy(List<MbDimensionsDto> dimensions, Long questionBankId) {

        checkIsDimensionsDtoLegal(dimensions);
        //批量添加
        dimensions.forEach(mbDimensionDto -> {
            MbDimension mbDimension = new MbDimension();
            BeanUtils.copyProperties(mbDimensionDto, mbDimension);
            mbDimension.setQuestionBankId(questionBankId);
            mbDimensionService.save(mbDimension);
        });

        return true;
    }


    private void checkIsDimensionsDtoLegal(List<MbDimensionsDto> dimensions) {

        dimensions.forEach(dimension -> {
            if (StringUtils.isEmpty(dimension.getName())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR);
            }
            if (dimension.getWeight() < 0) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR);
            }
            List<MbDimensionsDto> children = dimension.getChildren();
            if (children != null && children.size() != 0) {
                //递归校验
                checkIsDimensionsDtoLegal(children);
            }
        });
    }
}

二级维度

scss复制代码@Component
public class SaveTwoDimensionStrategy implements SaveDimensionStrategy {


    @Resource
    private MbDimensionService mbDimensionService;

    @Override
    public Integer getType() {
        return QuestionBankSettingTypeEnum.二级维度.getValue();
    }

    @Override
    public boolean saveDimensionStrategy(List<MbDimensionsDto> dimensions, Long questionBankId) {
        checkIsDimensionsDtoLegal(dimensions);
        //新增树状dimensions
        dimensions.forEach(mbDimensionDto -> {
            MbDimension mbDimension = new MbDimension();
            BeanUtils.copyProperties(mbDimensionDto, mbDimension);
            mbDimension.setQuestionBankId(questionBankId);
            mbDimensionService.save(mbDimension);
            Long fatherId = mbDimension.getId();
            List<MbDimensionsDto> children = mbDimensionDto.getChildren();

            if(children == null){
                throw new BusinessException(ErrorCode.PARAMS_ERROR,"二级维度中,父维度一定要有子维度");
            }

            children.forEach(son -> {
                MbDimension mbDimensionSon = new MbDimension();
                BeanUtils.copyProperties(son, mbDimensionSon);
                mbDimensionSon.setQuestionBankId(questionBankId);
                //新增后的dimension的id,即为chidren dimension的父亲id.
                mbDimensionSon.setParentId(fatherId);
                mbDimensionService.save(mbDimensionSon);
            });
        });
        return true;
    }


    private void checkIsDimensionsDtoLegal(List<MbDimensionsDto> dimensions) {
        dimensions.forEach(dimension -> {
            if (StringUtils.isEmpty(dimension.getName())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR);
            }
            if (dimension.getWeight() < 0) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR);
            }
            List<MbDimensionsDto> children = dimension.getChildren();
            if (children != null && children.size() != 0) {
                //递归校验
                checkIsDimensionsDtoLegal(children);
            }
        });
    }
}

由此可见,我们做的不过是把行为抽象成了接口,利用三个实现类去规范不同的行为。

这里实现类都加上了@Component注解,让它变成一个个Bean,同时Spring去管理。

定义Map和runner

现在已经有了一系列解决方案,也就是我们写的一个个实现类。现在我们需要一个容器去把这一个个的解决方案跑起来!

见名知义,runner就是为此而生的。

我们定义一个 SaveDimensionStrategyRunner

java复制代码@Component
public interface SaveDimensionStrategyRunner {
    /**
     * 根据 type 字段执行不同策略
     * @return
     */
    boolean saveDimension(List<MbDimensionsDto> dimensionsDtos, Long questionBankId,Integer type);
}

接下来,我们定义实现类。

typescript复制代码/**
 * 用来管理策略模式使用的bean,放入map进行管理
 */
@Configuration
public class StrategyConfig {

    @Bean
    public SaveDimensionStrategyRunner strategyRunner(List<SaveDimensionStrategy> strategies) {
        Map<Integer, SaveDimensionStrategy> strategyMap = strategies.stream()
                .collect(Collectors.toMap(SaveDimensionStrategy::getType, s -> s));
        return (dimensionsDtoList,questionBankId,type) -> strategyMap.get(type).saveDimensionStrategy(dimensionsDtoList,questionBankId);
    }

}

这段代码做了几件事情:

  1. 由于之前定义的strategy被打上了@Component注解,因此,他们会被spring管理,注入到这个方法的参数里。
  2. 根据传入的各个strategy,我们将他们的type设置为key,本身设置为value,放入map集合中。
  3. return语句中是 对SaveDimensionStrategyRunner这个接口中的方法的实现,也就是说,我们返回的就是刚刚定义的接口的实现类,又由于加上了@Bean注解,该实例会被spring所管理。

使用

我们在需要这个方法的实现类中注入runner

java复制代码    @Resource
    private SaveDimensionStrategyRunner saveDimensionStrategyRunner;

然后直接调用它的方法就可以啦!