侧边栏壁纸

SpringBoot基于工厂+策略模式的题目处理思路详解

2024年10月12日 180阅读 0评论 0点赞

在软件开发中,常常会遇到需要根据不同类型执行不同处理逻辑的场景。如果直接使用大量的 if-elseswitch-case,代码会变得冗长且难以维护。为了解决这个问题,我们可以采用策略模式工厂模式,使代码结构更加清晰,且方便扩展。

下面,我将结合代码,详细讲解这种设计思路,并说明实现后数据的流向。特别是,对于工厂类的设计,我们将进行详细的拆分和讲解。

一、设计思路

1. 问题背景

有一个试题需求,需要处理多种类型的题目:

  • 单选题(RADIO)
  • 多选题(MULTIPLE)
  • 判断题(JUDGE)
  • 简答题(BRIEF)

每种题型的处理逻辑不同,如果直接在代码中大量使用条件判断,不利于代码的维护和扩展。

2. 设计目标

  • 解耦不同题型的处理逻辑:每种题型的处理逻辑独立,互不影响。
  • 提高代码的可扩展性:新增题型时,尽量不修改原有代码。
  • 简化业务层的代码:业务层不需要关注具体的处理逻辑。

3. 采用的设计模式

  • 策略模式(Strategy Pattern):定义一系列算法,将每个算法封装起来,使它们可以互相替换,且算法的变化不会影响到使用算法的客户。
  • 工厂模式(Factory Pattern):定义一个创建对象的接口,让子类决定实例化哪个类。

4. 设计步骤

  1. 定义策略接口:抽象出题目处理的通用接口。
  2. 实现具体策略类:为每种题型创建一个处理器,实现策略接口。
  3. 创建工厂类:根据题目类型,返回对应的处理器。
  4. 业务层使用工厂获取处理器:业务层调用处理器的方法,完成题目的处理。

二、代码实现

m262tvr5.png

1. 定义策略接口 SubjectTypeHandler

public interface SubjectTypeHandler {

    /**
     * 获取处理器对应的题目类型
     */
    SubjectInfoTypeEnum getHandlerType();

    /**
     * 添加题目的方法
     */
    void add(SubjectInfoBO subjectInfoBO);
}
  • getHandlerType():返回处理器对应的题目类型枚举。
  • add(SubjectInfoBO subjectInfoBO):处理添加题目的方法。

2. 实现具体策略类

(1)单选题处理器 RadioTypeHandler

public class RadioTypeHandler implements SubjectTypeHandler {

    @Override
    public SubjectInfoTypeEnum getHandlerType() {
        return SubjectInfoTypeEnum.RADIO;
    }

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        // 实现单选题的添加逻辑
        System.out.println("添加单选题:" + subjectInfoBO);
    }
}

(2)多选题处理器 MultipleTypeHandler

public class MultipleTypeHandler implements SubjectTypeHandler {

    @Override
    public SubjectInfoTypeEnum getHandlerType() {
        return SubjectInfoTypeEnum.MULTIPLE;
    }

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        // 实现多选题的添加逻辑
        System.out.println("添加多选题:" + subjectInfoBO);
    }
}

(3)判断题处理器 JudgeTypeHandler

public class JudgeTypeHandler implements SubjectTypeHandler {

    @Override
    public SubjectInfoTypeEnum getHandlerType() {
        return SubjectInfoTypeEnum.JUDGE;
    }

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        // 实现判断题的添加逻辑
        System.out.println("添加判断题:" + subjectInfoBO);
    }
}

(4)简答题处理器 BriefTypeHandler

public class BriefTypeHandler implements SubjectTypeHandler {

    @Override
    public SubjectInfoTypeEnum getHandlerType() {
        return SubjectInfoTypeEnum.BRIEF;
    }

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        // 实现简答题的添加逻辑
        System.out.println("添加简答题:" + subjectInfoBO);
    }
}

3. 创建工厂类 SubjectTypeHandlerFactory

下面,我们对工厂类的设计进行详细的拆分和讲解。

(1)工厂类的定义

@Component
public class SubjectTypeHandlerFactory implements InitializingBean {

    // 注入所有实现了 SubjectTypeHandler 接口的处理器
    @Resource
    private List<SubjectTypeHandler> subjectTypeHandlerList;

    private Map<SubjectInfoTypeEnum, SubjectTypeHandler> handlerMap = new HashMap<>();

    /**
     * 根据题目类型获取对应的处理器
     */
    public SubjectTypeHandler getHandler(int subjectType) {
        SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType);
        return handlerMap.get(typeEnum);
    }

    @Override
    public void afterPropertiesSet() {
        // 将处理器按题目类型存入 Map
        for (SubjectTypeHandler handler : subjectTypeHandlerList) {
            handlerMap.put(handler.getHandlerType(), handler);
        }
    }
}

一定要注意添加@Component注解,表示将代码交给springboot托管,否则代码不会生效,这样在springboot在启动完成之后,我们的Handler就被装配进了spring容器了。

(2)成员变量和注入

// 注入所有实现了 SubjectTypeHandler 接口的处理器
@Resource
private List<SubjectTypeHandler> subjectTypeHandlerList;

private Map<SubjectInfoTypeEnum, SubjectTypeHandler> handlerMap = new HashMap<>();
  • subjectTypeHandlerList:通过 @Resource 注解,Spring 会自动注入容器中所有实现了 SubjectTypeHandler 接口的 Bean。
  • handlerMap:用于存储题目类型与对应处理器的映射关系。

(3)初始化方法 afterPropertiesSet()

@Override
public void afterPropertiesSet() {
    // 将处理器按题目类型存入 Map
    for (SubjectTypeHandler handler : subjectTypeHandlerList) {
        handlerMap.put(handler.getHandlerType(), handler);
    }
}
  • 作用:在 Bean 初始化完成后,Spring 会调用 afterPropertiesSet() 方法。
  • 逻辑

    • 遍历 subjectTypeHandlerList,获取每个处理器。
    • 调用处理器的 getHandlerType() 方法,获取对应的题目类型。
    • 将题目类型和处理器存入 handlerMap,建立映射关系。
  • 目的:将所有的处理器按照题目类型注册到工厂中,方便后续根据题目类型获取对应的处理器。

(4)获取处理器方法 getHandler(int subjectType)

public SubjectTypeHandler getHandler(int subjectType) {
    SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType);
    return handlerMap.get(typeEnum);
}
  • 参数subjectType,题目类型的整数表示。
  • 逻辑

    • 调用 SubjectInfoTypeEnum.getByCode(subjectType),将整数类型转换为对应的枚举类型 SubjectInfoTypeEnum
    • handlerMap 中获取对应的处理器。
  • 返回值SubjectTypeHandler,对应题目类型的处理器。

(5)类注解 @Component

@Component
public class SubjectTypeHandlerFactory implements InitializingBean { ... }
  • @Component:将工厂类声明为一个 Spring Bean,交由 Spring 容器管理。
  • implements InitializingBean:实现 InitializingBean 接口,目的是在 Bean 初始化后执行 afterPropertiesSet() 方法。

4. 业务层调用

@Service
public class SubjectService {

    @Autowired
    private SubjectTypeHandlerFactory handlerFactory;

    public void addSubject(SubjectInfoBO subjectInfoBO) {
        // 获取对应的处理器
        SubjectTypeHandler handler = handlerFactory.getHandler(subjectInfoBO.getType());
        if (handler != null) {
            // 调用处理器的添加方法
            handler.add(subjectInfoBO);
        } else {
            throw new IllegalArgumentException("未知的题目类型");
        }
    }
}
  • 注入工厂类@AutowiredSubjectTypeHandlerFactory 注入到业务层。
  • 添加题目方法

    • 根据 subjectInfoBO.getType() 获取题目类型。
    • 调用工厂的 getHandler() 方法获取对应的处理器。
    • 调用处理器的 add() 方法,处理题目添加逻辑。

三、实现后的数据流向

1. 数据流整体概述

当需要添加一个新题目时,数据流向如下:

  1. 接收请求SubjectService.addSubject() 接收 SubjectInfoBO 对象,包含题目的详细信息。
  2. 获取处理器SubjectTypeHandlerFactory.getHandler() 根据题目类型返回对应的处理器。
  3. 处理题目:调用处理器的 add() 方法,执行具体的处理逻辑。
  4. 完成添加:处理器完成题目的添加操作。

2. 数据流详细步骤

(1)接收题目信息

SubjectInfoBO subjectInfoBO = new SubjectInfoBO();
subjectInfoBO.setType(1); // 假设 1 代表单选题
subjectInfoBO.setContent("题目内容");
// ... 设置其他属性

subjectService.addSubject(subjectInfoBO);
  • 创建题目对象:实例化 SubjectInfoBO,设置题目类型和内容等属性。
  • 调用业务方法:调用 SubjectService.addSubject() 方法,传入题目对象。

(2)业务层获取处理器

public void addSubject(SubjectInfoBO subjectInfoBO) {
    // 获取处理器
    SubjectTypeHandler handler = handlerFactory.getHandler(subjectInfoBO.getType());
    // ...
}
  • 获取题目类型:从 subjectInfoBO 中获取题目类型。
  • 调用工厂方法:使用工厂的 getHandler() 方法获取对应的处理器。

(3)工厂返回处理器

public SubjectTypeHandler getHandler(int subjectType) {
    SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType);
    return handlerMap.get(typeEnum);
}
  • 类型转换:将整数类型的题目类型转换为枚举类型。
  • 获取处理器:从 handlerMap 中根据枚举类型获取处理器。

(4)处理器处理题目

if (handler != null) {
    handler.add(subjectInfoBO);
} else {
    throw new IllegalArgumentException("未知的题目类型");
}
  • 调用处理方法:如果获取到了处理器,调用其 add() 方法。
  • 异常处理:如果没有对应的处理器,抛出异常。

处理器的 add() 方法,例如单选题处理器:

public class RadioTypeHandler implements SubjectTypeHandler {

    @Override
    public void add(SubjectInfoBO subjectInfoBO) {
        // 实现单选题的添加逻辑
        System.out.println("添加单选题:" + subjectInfoBO);
        // 例如,将题目信息保存到数据库
    }
}
  • 具体处理逻辑:处理器执行特定题型的添加逻辑,例如数据校验、数据库操作等。

3. 数据流示意图

用户请求 --> SubjectService.addSubject() --> SubjectTypeHandlerFactory.getHandler() --> 返回对应处理器
    |
    v
调用处理器的 add() 方法 --> 处理器执行添加逻辑 --> 完成题目添加

四、工厂类设计的详细讲解

1. 工厂类的职责

SubjectTypeHandlerFactory 的主要职责是根据题目类型返回对应的处理器。这一过程包含:

  • 初始化处理器映射关系:在工厂类初始化时,将所有的处理器注册到一个映射中。
  • 提供获取处理器的方法:根据题目类型,返回相应的处理器实例。

2. 工厂类的实现细节

(1)注入所有处理器

@Resource
private List<SubjectTypeHandler> subjectTypeHandlerList;
  • @Resource 注解:由 Spring 自动注入容器中所有实现了 SubjectTypeHandler 接口的 Bean。
  • 目的:获取所有的处理器实例,方便后续的映射注册。

(2)定义处理器映射

private Map<SubjectInfoTypeEnum, SubjectTypeHandler> handlerMap = new HashMap<>();
  • handlerMap:用于存储题目类型和处理器实例的对应关系。
  • 类型安全:使用 SubjectInfoTypeEnum 作为键,确保类型的一致性。

(3)初始化处理器映射

@Override
public void afterPropertiesSet() {
    for (SubjectTypeHandler handler : subjectTypeHandlerList) {
        handlerMap.put(handler.getHandlerType(), handler);
    }
}
  • afterPropertiesSet() 方法:由 InitializingBean 接口提供,在 Bean 的属性设置完成后由 Spring 自动调用。
  • 处理逻辑

    • 遍历 subjectTypeHandlerList 中的所有处理器。
    • 调用每个处理器的 getHandlerType() 方法,获取其支持的题目类型。
    • 将题目类型和处理器实例存入 handlerMap

(4)获取处理器的方法

public SubjectTypeHandler getHandler(int subjectType) {
    SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType);
    return handlerMap.get(typeEnum);
}
  • 参数说明subjectType,题目类型的整数表示。
  • 步骤解析

    1. 类型转换:调用 SubjectInfoTypeEnum.getByCode(subjectType) 方法,将整数类型转换为枚举类型。

      SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType);
    2. 获取处理器:从 handlerMap 中根据枚举类型获取对应的处理器实例。

      return handlerMap.get(typeEnum);
  • 返回值:对应题目类型的处理器,如果不存在则返回 null

(5)异常处理(可选)

在实际应用中,可能需要对获取不到处理器的情况进行处理,可以在 getHandler() 方法中添加异常处理:

public SubjectTypeHandler getHandler(int subjectType) {
    SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType);
    SubjectTypeHandler handler = handlerMap.get(typeEnum);
    if (handler == null) {
        throw new IllegalArgumentException("未知的题目类型:" + subjectType);
    }
    return handler;
}
  • 目的:避免业务层需要处理 null 值,将异常抛出,提高代码的健壮性。

(6)工厂类的优势

  • 集中管理:所有处理器的实例化和获取都由工厂类负责,方便管理。
  • 解耦业务层和处理器:业务层无需关心处理器的实现细节,只需通过工厂获取即可。
  • 易于扩展:新增题型处理器时,只需实现 SubjectTypeHandler 接口,并将其注册到 Spring 容器中,无需修改工厂类的代码。

3. 工厂类的完整代码

@Component
public class SubjectTypeHandlerFactory implements InitializingBean {

    @Resource
    private List<SubjectTypeHandler> subjectTypeHandlerList;

    private Map<SubjectInfoTypeEnum, SubjectTypeHandler> handlerMap = new HashMap<>();

    public SubjectTypeHandler getHandler(int subjectType) {
        SubjectInfoTypeEnum typeEnum = SubjectInfoTypeEnum.getByCode(subjectType);
        SubjectTypeHandler handler = handlerMap.get(typeEnum);
        if (handler == null) {
            throw new IllegalArgumentException("未知的题目类型:" + subjectType);
        }
        return handler;
    }

    @Override
    public void afterPropertiesSet() {
        for (SubjectTypeHandler handler : subjectTypeHandlerList) {
            handlerMap.put(handler.getHandlerType(), handler);
        }
    }
}
0
打赏

—— 评论区 ——

昵称
邮箱
网址
取消
人生倒计时
舔狗日记