工厂 + 策略设计模式实现题目新增解耦

106 阅读3分钟

题目架构设计

题目分为单选,多选,判断,简单,四种数据类型,在设计数据的时候,我们去拆分成了题目的主表和其他对应的表来做。

题目主表:

image.png

单选表:

image.png

新增题目

注意:采取工厂+策略的模式去做扩展,现在有四种题型,未来无论加多少种,我们都可以不用动主流程。

后期会结合es 做题目的查重。为搜索做准备。

工厂 + 策略实现

1.首先在common公共层定义题目类型枚举类

package com.ssm.subject.common.enums;

import lombok.Getter;

/**
 * 题目类型枚举
 * 1.单选 2.多选 3.判断 4.简答
 */
@Getter
public enum SubjectInfoTypeEnum {

    RADIO(1, "单选"),
    MULTIPLE(2, "多选"),
    JUDGE(3, "判断"),
    BRIEF(4, "简答");

    private Integer code;

    private String desc;

    SubjectInfoTypeEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public static SubjectInfoTypeEnum getByCode(int code) {
        for(SubjectInfoTypeEnum subjectInfoTypeEnum : SubjectInfoTypeEnum.values()) {
            if(subjectInfoTypeEnum.code == code) {
                return subjectInfoTypeEnum;
            }
        }
        return null;
    }

}

2.使用策略模式定义题目父类和实现类

父接口中定义规则,实现类中实现每种题目的具体逻辑

package com.ssm.subject.domain.handler.subejct;

import com.ssm.subject.common.enums.SubjectInfoTypeEnum;
import com.ssm.subject.domain.bo.SubjectInfoBO;

public interface SubejctTypeHandler {
    /**
     * 获取每个实体类对应哪个枚举属性(枚举身份的识别)
     * @return
     */
    SubjectInfoTypeEnum getHandlerType();

    /**
     * 每个实体类真正的新增题目方法
     * @param subjectInfoBO
     */
    void add(SubjectInfoBO subjectInfoBO);
}
package com.ssm.subject.domain.handler.subejct;

import com.ssm.subject.common.enums.SubjectInfoTypeEnum;
import com.ssm.subject.domain.bo.SubjectAnswerBO;
import com.ssm.subject.domain.bo.SubjectInfoBO;
import com.ssm.subject.domain.convert.SubjectRadioConvert;
import com.ssm.subject.infra.basic.entity.SubjectRadio;
import com.ssm.subject.infra.basic.service.SubjectRadioService;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 单选策略类
 */
@Component
public class RadioTypeHandle implements SubejctTypeHandler {
    @Resource
    private SubjectRadioService subjectRadioService;
    @Override
    public SubjectInfoTypeEnum getHandlerType() {
        return SubjectInfoTypeEnum.RADIO;
    }

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        //单选题目的插入
        List<SubjectRadio> subjectRadioList = new ArrayList<>(); //一个选项为一条数据,用List存
        List<SubjectAnswerBO> optionList = subjectInfoBO.getOptionList();
        //为空,直接返回,空集合使用foreach会空指针
        if(CollectionUtils.isEmpty(optionList)) {
            return;
        }
        optionList.forEach(subjectAnswerBO -> {
            SubjectRadio subjectRadio = SubjectRadioConvert.INSTANCE.subjectAnswerBoToRadio(subjectAnswerBO);
            subjectRadio.setSubjectId(subjectInfoBO.getId());
            subjectRadioList.add(subjectRadio);
        });
        subjectRadioService.batchInsert(subjectRadioList);
    }
}
package com.ssm.subject.domain.handler.subejct;

import com.ssm.subject.common.enums.SubjectInfoTypeEnum;
import com.ssm.subject.domain.bo.SubjectAnswerBO;
import com.ssm.subject.domain.bo.SubjectInfoBO;
import com.ssm.subject.domain.convert.SubjectMultipleConvert;
import com.ssm.subject.infra.basic.entity.SubjectMultiple;
import com.ssm.subject.infra.basic.service.SubjectMappingService;
import com.ssm.subject.infra.basic.service.SubjectMultipleService;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 多选策略类
 */
@Component
public class MultipleTypeHandle implements SubejctTypeHandler{
    @Resource
    private SubjectMultipleService subjectMultipleService;
    @Override
    public SubjectInfoTypeEnum getHandlerType() {
        return SubjectInfoTypeEnum.MULTIPLE;
    }

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        //多选题目的插入
        List<SubjectAnswerBO> optionList = subjectInfoBO.getOptionList();
        List<SubjectMultiple> subjectMultipleList = new ArrayList<>();
        if(CollectionUtils.isEmpty(optionList)) {
            return;
        }
        optionList.forEach(subjectAnswerBO -> {
            SubjectMultiple subjectMultiple = SubjectMultipleConvert.INSTANCE.subjectAnswerBoToMultiple(subjectAnswerBO);
            subjectMultiple.setSubjectId(subjectInfoBO.getId());
            subjectMultipleList.add(subjectMultiple);
        });
        subjectMultipleService.batchInsert(subjectMultipleList);
    }
}
package com.ssm.subject.domain.handler.subejct;

import com.ssm.subject.common.enums.SubjectInfoTypeEnum;
import com.ssm.subject.domain.bo.SubjectAnswerBO;
import com.ssm.subject.domain.bo.SubjectInfoBO;
import com.ssm.subject.domain.convert.SubjectJudgeConvert;
import com.ssm.subject.infra.basic.entity.SubjectJudge;
import com.ssm.subject.infra.basic.service.SubjectJudgeService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 判断策略类
 */
@Component
public class JudgeTypeHandle implements SubejctTypeHandler{
    @Resource
    private SubjectJudgeService subjectJudgeService;
    @Override
    public SubjectInfoTypeEnum getHandlerType() {
        return SubjectInfoTypeEnum.JUDGE;
    }

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        //判断题目的插入(判断题,列表中只有一条是否正确数据)
        SubjectAnswerBO subjectAnswerBO = subjectInfoBO.getOptionList().get(0);
        SubjectJudge subjectJudge = SubjectJudgeConvert.INSTANCE.subjectAnswerBoToJudge(subjectAnswerBO);
        subjectJudge.setSubjectId(subjectInfoBO.getId());
        subjectJudgeService.insert(subjectJudge);
    }
}
package com.ssm.subject.domain.handler.subejct;

import com.ssm.subject.common.enums.SubjectInfoTypeEnum;
import com.ssm.subject.domain.bo.SubjectInfoBO;
import com.ssm.subject.domain.convert.SubjectBriefConvert;
import com.ssm.subject.infra.basic.entity.SubjectBrief;
import com.ssm.subject.infra.basic.service.SubjectBriefService;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * 简答策略类
 */
@Component
public class BriefTypeHandle implements SubejctTypeHandler{
    @Resource
    private SubjectBriefService subjectBriefService;
    @Override
    public SubjectInfoTypeEnum getHandlerType() {
        return SubjectInfoTypeEnum.BRIEF;
    }

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        //简答题目的插入
        SubjectBrief subjectBrief = SubjectBriefConvert.INSTANCE.subjectInfoBoToBrief(subjectInfoBO);
        subjectBrief.setSubjectId(subjectInfoBO.getId());
        subjectBriefService.insert(subjectBrief);
    }
}

3.最后使用工厂模式根据入参时的题目类型来获取不同题目

package com.ssm.subject.domain.handler.subejct;

import com.ssm.subject.common.enums.SubjectInfoTypeEnum;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 题目类型工厂类
 */
@Component
public class SubjectTypeHandlerFactory implements InitializingBean {
    @Resource //可获取所有实现类(实现类要被加入到IOC容器)
    private List<SubejctTypeHandler> subejctTypeHandlerList;

    private Map<SubjectInfoTypeEnum, SubejctTypeHandler> subejctTypeHandlerMap = new HashMap<>();

    /**
     * 对外提供根据id获取对应的题目策略类
     * @param subjectType
     * @return
     */
    public SubejctTypeHandler getHandler(int subjectType) {
        SubjectInfoTypeEnum subjectInfoTypeEnum = SubjectInfoTypeEnum.getByCode(subjectType);
        return subejctTypeHandlerMap.get(subjectInfoTypeEnum);
    }

    /**
     * 实现InitializingBean后可重写该方法,实现当前bean初始化完成后执行该方法为handlerMap赋值
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        for (SubejctTypeHandler subjectTypeHandler : subejctTypeHandlerList) {
            subejctTypeHandlerMap.put(subjectTypeHandler.getHandlerType(), subjectTypeHandler);
        }
    }
}

优势

在外部domain层中,不需嵌套大量if进行判断题目类型和每个类型的具体逻辑,解耦,可维护性强

@Override
@Transactional(rollbackFor = Exception.class)
public void add(SubjectInfoBO subjectInfoBO) {
    if(log.isInfoEnabled()) {
        log.info("SubjectInfoDomainService.add.subjectInfoBO:{}", JSON.toJSONString(subjectInfoBO));
    }
    //先插入主表info数据
    SubjectInfo subjectInfo = SubjectInfoBOConvert.INSTANCE.subjectInfoBoToInfo(subjectInfoBO);
    subjectInfoServices.insert(subjectInfo);
    //工厂 + 策略设计模式
    SubejctTypeHandler handler = subjectTypeHandlerFactory.getHandler(subjectInfo.getSubjectType());
    subjectInfoBO.setId(subjectInfo.getId()); //info表开启了主键映射,新增完会把id值赋值到实体类上
    handler.add(subjectInfoBO);
    //把题目、标签、分类的映射关系同步到mapping表中(每个题目对应的每个分类对应的每个标签都为一条数据,即1个题目在有3个标签且属于两个分类就有6条数据)
    List<Long> categoryIds = subjectInfoBO.getCategoryIds();
    List<Long> labelIds = subjectInfoBO.getLabelIds();
    //分类id和标签id在入口已经判空过了,在domain层肯定有值
    List<SubjectMapping> subjectMappingList = new ArrayList<>();
    categoryIds.forEach(categoryId -> {
        labelIds.forEach(labelId -> {
            SubjectMapping subjectMapping = new SubjectMapping();
            subjectMapping.setCategoryId(categoryId);
            subjectMapping.setLabelId(labelId);
            subjectMapping.setSubjectId(subjectInfo.getId());
            subjectMappingList.add(subjectMapping);
        });
    });
    subjectMappingService.batchInsert(subjectMappingList);

}