天天看點

利用政策模式結合Springboot架構

作者:馬士兵老師

利用政策模式解決多條件問題

問題重制

這是公司代碼裡面的一個接口,我需要根據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;
           

然後直接調用它的方法就可以啦!

作者:nika_yo_nihao

連結:https://juejin.cn/post/7213310111624740925