利用策略模式解决多条件问题
问题重现
这是公司代码里面的一个接口,我需要根据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;
然后直接调用它的方法就可以啦!