利用策略模式结合Springboot框架
利用策略模式解决多条件问题
问题重现
这是公司代码里面的一个接口,我需要根据type的不同,去决定要不要存储里面的对象。
ini复制代码 @Transactional(rollbackFor = Exception.class) @Override public boolean saveDimensionsByQuestionBankId(List 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 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 dimensions, Long questionBankId); }
我们目前有三个类,都需要实现这个接口,来进行不同的行为,在这里就是对维度的不同操作。
无维度
由于无维度无需处理,直接返回true即可
typescript复制代码@Component public class SaveNoDimensionStrategy implements SaveDimensionStrategy { @Override public Integer getType() { return QuestionBankSettingTypeEnum.无维度.getValue(); } @Override public boolean saveDimensionStrategy(List 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 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 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 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 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 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 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 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 dimensionsDtos, Long questionBankId,Integer type); }
接下来,我们定义实现类。
typescript复制代码/** * 用来管理策略模式使用的bean,放入map进行管理 */ @Configuration public class StrategyConfig { @Bean public SaveDimensionStrategyRunner strategyRunner(List strategies) { Map strategyMap = strategies.stream() .collect(Collectors.toMap(SaveDimensionStrategy::getType, s -> s)); return (dimensionsDtoList,questionBankId,type) -> strategyMap.get(type).saveDimensionStrategy(dimensionsDtoList,questionBankId); } }
这段代码做了几件事情:
使用
我们在需要这个方法的实现类中注入runner
java复制代码 @Resource private SaveDimensionStrategyRunner saveDimensionStrategyRunner;
然后直接调用它的方法就可以啦!