策略模式+工厂模式实现单接口适配多审核节点

一、核心设计模式定义&设计初衷

1. 策略模式(Strategy Pattern)

定义 :一种行为型设计模式,定义一个统一的策略接口 ,将多个实现了该接口的具体策略类 封装为独立的业务逻辑单元,策略之间可互相替换,且不影响使用策略的客户端代码。
在你的业务中INodeAuditHandler策略接口 ,定义了validate(校验)、execute(执行)的统一方法契约;ApplyInformationFirst(进件第一步)、BankInsuranceBeginAudit(保银初审)是具体策略类,每个类封装一个审核节点的专属业务逻辑,彼此独立、可替换。

2. 工厂模式(简单工厂+Spring容器增强)

定义 :一种创建型设计模式,提供一个工厂类 封装对象的创建逻辑,客户端无需关心对象的创建细节,只需通过指定标识获取对应的对象实例。
在你的业务中NodeAuditHandlerFactory工厂类 ,封装了INodeAuditHandler所有实现类的获取逻辑;通过Spring容器自动注入的Map<String, INodeAuditHandler>管理所有策略类实例,客户端只需传入节点标识 (如applyInformationFirst),即可获取对应的策略类实例,无需手动创建。

3. 为什么要结合「策略模式+工厂模式」写?

你的原有代码是一个审核节点对应一个接口,存在「接口冗余、新增节点需修改原有代码、维护成本高」的问题;而结合两种模式,核心解决**「解耦」和「可扩展」** 两大核心痛点,同时适配「多节点审核、各节点逻辑独立、后续需持续新增节点」的业务特性,具体原因:

  1. 单靠策略模式:能实现业务逻辑解耦,但客户端(接口层)仍需手动注入/选择策略类,无法实现「单接口适配多节点」;
  2. 单靠工厂模式:能封装对象创建,但无统一的接口约束,各节点逻辑会杂乱无章,无法保证代码规范;
  3. 两者结合:策略模式保证业务逻辑的标准化、解耦性 ,工厂模式实现策略类的动态获取、单接口统一调度,完美适配你的进件审核业务。

4. 这样写的核心好处

  1. 符合开闭原则 :新增审核节点(如GPS安装审核、终审),仅需新增策略类(实现INodeAuditHandler),无需修改工厂类、通用接口等原有代码,做到「对扩展开放,对修改关闭」;
  2. 消除代码冗余:用1个通用接口替代N个节点专属接口,统一复用「校验→执行」的核心流程,避免重复编写接口方法和业务调用逻辑;
  3. 逻辑边界清晰 :每个审核节点的校验、业务逻辑封装在独立的策略类中,问题定位快(如进件第一步异常仅需排查ApplyInformationFirst),便于团队分工维护;
  4. 降低耦合度:接口层、工厂层、策略层各司其职,接口层仅负责接收请求,工厂层仅负责获取策略类,策略层仅负责实现专属业务,层与层之间无硬编码依赖;
  5. 扩展性极强:后续可无缝扩展节点优先级、节点组合校验等功能,无需重构核心架构。

二、基于你的代码的完整实现方案(直接复用)

所有代码基于你提供的INodeAuditHandlerApplyInformationFirstBankInsuranceBeginAudit改造,无需修改原有策略类,仅新增工厂类和通用接口,快速落地。

1. 原有代码(无需修改,基础支撑)

(1)策略接口:INodeAuditHandler
java 复制代码
public interface INodeAuditHandler {
    R validate(BladeUser bladeUser, Object... params);
    R execute(BladeUser bladeUser,Object... params) throws JsonProcessingException;
}
(2)具体策略类1:进件第一步(已标注@Component,Bean名称:applyInformationFirst)
java 复制代码
@Component("applyInformationFirst")
@AllArgsConstructor
@Slf4j
public class ApplyInformationFirst implements INodeAuditHandler {
    private final String logTags = "====>进件初步提交";
    private final InformationService informationService;

    @Override
    public R validate(BladeUser bladeUser, Object... params) {
        log.info(logTags+"校验!!!");
        InformationDTO info = (InformationDTO) params[0];
        StringHelper.IsEmptyOrNullToMes(info.getCreateUser(), "业务员不能为空");
        return null;
    }

    @Override
    public R execute(BladeUser bladeUser, Object... params) throws JsonProcessingException {
        log.info(logTags+"业务逻辑!!!");
        InformationDTO info = (InformationDTO) params[0];
        R r = informationService.applyInfomationFirst( info);
        if(r.isSuccess() && r.getCode()==500){
            return r;
        }
        return r;
    }
}
(3)具体策略类2:保银初审(已标注@Component,Bean名称:bankInsuranceBeginAudit)
java 复制代码
@Component("bankInsuranceBeginAudit")
@AllArgsConstructor
@Slf4j
public class BankInsuranceBeginAudit implements INodeAuditHandler {
    private final String logTags = "====>初审";
    private final InformationFlowableService informationFlowableService;
    private final InformationMaterialsTempServiceImpl informationMaterialsTempService;
    private final InformationMaterialsService informationMaterialsService;

    @Override
    public R validate(BladeUser bladeUser, Object... params) {
        log.info(logTags + "校验!!!");
        NodeDTO nodeDTO = (NodeDTO) params[0];
        String operatorType = nodeDTO.getOperatorType();
        if (!FlowableOperatorType.RESOLVE.getValue().equals(operatorType)
                && !FlowableOperatorType.REJECT_NODE.getValue().equals(operatorType)) {
            throw new ServiceException("该节点仅支持通过/拒单操作");
        }
        if (StringUtil.isBlank(nodeDTO.getRemark())) {
            throw new ServiceException("请填写审核意见");
        }
        return null;
    }

    @Override
    @Transactional
    public R execute(BladeUser bladeUser, Object... params) {
        log.info(logTags + "业务逻辑!!!");
        NodeDTO nodeDTO = (NodeDTO) params[0];
        nodeDTO.setNodeName(FlowNodeEnum.BANK_INSURANCE_BEGIN_AUDIT.getDesc());
        nodeDTO.setNodeKey(FlowNodeEnum.BANK_INSURANCE_BEGIN_AUDIT.getNode());
        Long informationId = nodeDTO.getInformationId();
        return informationFlowableService.doReview(nodeDTO, bladeUser);
    }
}

2. 新增代码(核心改造,2个类即可实现)

(1)工厂类:NodeAuditHandlerFactory(策略类仓库,统一获取逻辑)
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;

/**
 * 节点审核处理器工厂类
 * 封装INodeAuditHandler所有实现类的获取逻辑,Spring自动注入所有策略类到Map
 */
@Component
public class NodeAuditHandlerFactory {
    /**
     * 核心:Spring容器启动时自动注入
     * Key:策略类的@Component注解值(如applyInformationFirst、bankInsuranceBeginAudit)
     * Value:对应的策略类实例(如ApplyInformationFirst、BankInsuranceBeginAudit)
     */
    @Autowired
    private Map<String, INodeAuditHandler> auditHandlerMap;

    /**
     * 根据节点标识获取对应的策略类实例
     * @param handlerName 节点标识(与策略类@Component注解值完全一致)
     * @return 对应的INodeAuditHandler实现类
     * @throws ServiceException 未找到对应策略类时抛出明确异常,便于排查
     */
    public INodeAuditHandler getHandler(String handlerName) {
        INodeAuditHandler handler = auditHandlerMap.get(handlerName);
        if (handler == null) {
            throw new ServiceException("未找到对应的审核节点处理器,节点标识:" + handlerName + ",请检查策略类@Component注解值是否正确");
        }
        return handler;
    }
}
(2)通用接口控制器:CommonNodeAuditController(单接口适配所有节点)
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

/**
 * 通用审核节点控制器
 * 一个接口适配所有INodeAuditHandler实现类,无需为每个节点单独写接口
 */
@RestController
@RequestMapping("/api/node/audit")
public class CommonNodeAuditController {
    // 注入工厂类,通过工厂类动态获取策略类
    @Autowired
    private NodeAuditHandlerFactory handlerFactory;

    /**
     * 通用审核接口:核心入口,所有审核节点请求均走此接口
     * @param handlerName 路径参数:节点标识(与策略类@Component注解值一致)
     * @param params      请求体:节点入参(兼容InformationDTO、NodeDTO等所有入参类型)
     * @param bladeUser   登录用户信息(原有业务参数,Spring自动注入,无需修改)
     * @return R 审核结果(与原有业务返回格式一致)
     */
    @PostMapping("/{handlerName}")
    public R audit(
            @PathVariable String handlerName,  // 核心:从URL获取节点标识,匹配策略类
            @RequestBody @Valid Object params, // 通用入参,兼容所有节点的入参DTO
            BladeUser bladeUser               // 原有用户参数,保持业务一致性
    ) throws JsonProcessingException {
        // 1. 从工厂类获取对应的策略类实例
        INodeAuditHandler nodeHandler = handlerFactory.getHandler(handlerName);
        // 2. 执行节点校验逻辑(与原有业务逻辑完全一致)
        R validateResult = nodeHandler.validate(bladeUser, params);
        if (validateResult != null) { // 校验失败,直接返回结果
            return validateResult;
        }
        // 3. 校验通过,执行节点核心业务逻辑
        return nodeHandler.execute(bladeUser, params);
    }
}

三、方案实现流程(请求全过程,举2个核心例子)

以「进件第一步」和「保银初审」为例,从前端请求后端返回结果,一步一步拆解执行流程,清晰看到策略模式+工厂模式的联动逻辑。

例子1:调用「进件第一步」审核节点

1. 前端/Postman发起请求
  • 请求URL:POST http://你的域名/api/node/audit/applyInformationFirst(核心:路径最后拼接节点标识applyInformationFirst,与策略类@Component注解值一致)
  • 请求体:传入InformationDTO类型的JSON参数(与你原有调用/applyInformationFirst的入参完全一致)
json 复制代码
{
  "createUser": "业务员007",
  "name": "李四",
  "cardNo": "310101199505056789",
  "phoneNumber": "13900139000",
  "imageInformationChildVO": {
    "image2_1": ["https://xxx.com/idcard/front.png", "https://xxx.com/idcard/back.png"]
  }
}
  • 请求头:携带登录态、Content-Type等原有参数,无需额外添加。
2. 后端执行流程
  1. 通用接口接收请求CommonNodeAuditControlleraudit方法接收请求,通过@PathVariable从URL中解析出handlerName=applyInformationFirst,请求体的JSON参数被封装为Object paramsBladeUser由Spring自动注入(如拦截器解析登录态);
  2. 工厂类获取策略类 :调用handlerFactory.getHandler("applyInformationFirst"),工厂类从auditHandlerMap中根据KeyapplyInformationFirst,取出对应的Value------ApplyInformationFirst策略类实例;
  3. 执行策略类校验逻辑 :调用ApplyInformationFirst.validate(bladeUser, params),将BladeUserInformationDTO参数传入,执行专属校验(业务员、姓名、身份证号、身份证正反面等非空校验),校验通过返回null
  4. 执行策略类业务逻辑 :调用ApplyInformationFirst.execute(bladeUser, params),执行进件第一步核心业务------调用informationService.applyInfomationFirst保存进件基础信息;
  5. 返回结果 :将业务执行结果封装为R对象,返回给前端,流程结束。

例子2:调用「保银初审」审核节点

1. 前端/Postman发起请求
  • 请求URL:POST http://你的域名/api/node/audit/bankInsuranceBeginAudit(核心:路径最后拼接节点标识bankInsuranceBeginAudit
  • 请求体:传入NodeDTO类型的JSON参数(与你原有调用/bankInsuranceBeginAudit的入参完全一致)
json 复制代码
{
  "informationId": 888888,
  "operatorType": "RESOLVE",
  "remark": "资料齐全,符合初审要求,同意通过",
  "nodeKey": "BANK_INSURANCE_BEGIN_AUDIT"
}
2. 后端执行流程
  1. 通用接口接收请求CommonNodeAuditControlleraudit方法接收请求,解析出handlerName=bankInsuranceBeginAudit,请求体封装为Object params,注入BladeUser
  2. 工厂类获取策略类 :调用handlerFactory.getHandler("bankInsuranceBeginAudit"),工厂类从Map中取出BankInsuranceBeginAudit策略类实例;
  3. 执行策略类校验逻辑 :调用BankInsuranceBeginAudit.validate(bladeUser, params),校验操作类型(仅支持通过/拒单)、审核意见非空,校验通过返回null
  4. 执行策略类业务逻辑 :调用BankInsuranceBeginAudit.execute(bladeUser, params),执行保银初审核心业务------设置节点名称/key、查询临时进件材料、同步材料/删除临时材料、调用审批流处理方法informationFlowableService.doReview
  5. 返回结果 :将审批流处理结果封装为R对象,返回给前端,流程结束。

核心共性

两个节点的请求流程完全一致 ,唯一区别是工厂类根据不同的handlerName取出了不同的策略类实例,实现了「单接口适配多节点」的核心目标,且所有原有业务逻辑均未修改,保证了业务一致性。

四、重点、难点、亮点解析(面试高频考点,体现专业性)

1. 重点(核心实现要点,必须掌握)

  1. 策略类的Bean名称唯一性 :所有实现INodeAuditHandler的策略类,@Component注解必须指定唯一的Bean名称 ,且该名称作为「节点标识」,是工厂类匹配的核心依据,必须与URL中的handlerName完全一致;
  2. Spring自动注入Map的规则@Autowired private Map<String, INodeAuditHandler> auditHandlerMap是核心,Spring容器启动时会自动扫描所有实现INodeAuditHandler的@Component类,以Bean名称为Key,实例为Value注入Map,无需手动维护;
  3. 通用入参的兼容性 :通用接口使用Object params接收请求体,配合策略接口的Object... params可变参数,完美兼容不同节点的入参DTO(如InformationDTONodeDTO),且策略类中已做强制类型转换,无需修改原有逻辑;
  4. 核心流程的复用:所有节点统一复用「工厂获取策略类→校验→执行」的流程,保证了业务逻辑的标准化,避免重复编写相同流程代码。

2. 难点(实现过程中易踩坑点,避坑关键)

  1. NoUniqueBeanDefinitionException异常 :若策略类未指定@Component的Bean名称,Spring会默认以「类名首字母小写」为Bean名称,若出现名称冲突(如类名相似),会抛出该异常------解决方式:为所有策略类显式指定唯一的Bean名称;
  2. 策略类未注入Map的问题 :若工厂类的auditHandlerMap为null或无对应策略类,大概率是策略类未标注@Component(Spring未扫描)、策略类未实现INodeAuditHandler接口、Bean名称拼写错误------解决方式 :检查策略类的注解和接口实现,保证Bean名称与URL中的handlerName完全一致;
  3. 类型转换异常 :若前端传入的入参DTO与策略类中强制转换的类型不一致,会抛出ClassCastException------解决方式 :前端需保证传入的入参与节点匹配(如进件第一步传InformationDTO),后端可在策略类中增加类型校验,提升健壮性;
  4. 事务失效问题 :若策略类的execute方法标注了@Transactional(如BankInsuranceBeginAudit),需保证策略类是Spring管理的Bean(已标注@Component),且事务注解的传播行为、隔离级别符合业务要求,避免事务失效。

3. 亮点(设计优势,面试中突出差异化,体现专业性)

  1. 架构解耦性强 :采用「接口层-工厂层-策略层」三层架构,各司其职:
    • 接口层:仅负责接收请求、返回结果,不涉及任何业务逻辑;
    • 工厂层:仅负责策略类的获取,不涉及业务执行;
    • 策略层:仅负责实现专属业务逻辑,不涉及请求处理和对象创建;
      层与层之间通过接口和标识交互,无硬编码依赖,符合「单一职责原则」。
  2. 极致的可扩展性 :新增审核节点时,仅需新增一个策略类 (实现INodeAuditHandler,标注@Component("唯一标识")),无需修改工厂类、通用接口等任何原有代码,完全符合「开闭原则」,适配业务快速迭代;
  3. Spring原生能力深度利用:未引入任何第三方框架,仅通过Spring的「依赖注入、Bean管理、自动扫描」等原生能力实现动态调度,降低了项目的依赖复杂度,且易于团队维护;
  4. 异常处理的标准化 :工厂类中对「未找到策略类」的场景抛出明确的ServiceException,配合项目全局异常处理器,可返回统一的错误格式,便于前端处理和问题排查,提升了系统的健壮性;
  5. 接口设计的RESTful规范 :通过URL路径(@PathVariable)传递节点标识,而非请求参数,符合RESTful接口设计规范,URL语义清晰(一眼可看出调用的是哪个审核节点),提升了接口的可读性和可维护性。

五、优化点(后续迭代方向,面试中体现思考深度)

基于当前方案,可根据业务复杂度和实际需求,做以下优化,让架构更健壮、功能更完善,面试中聊这些点,能体现出你对架构的深度思考:

  1. 增加策略类缓存(性能优化):若系统中审核节点数量多、调用频繁,可在工厂类中增加本地缓存(如Guava Cache、Caffeine),缓存策略类实例,避免多次从Spring Map中获取,提升性能(当前Spring Map是内存级,性能已较好,适合高并发场景优化);
  2. 节点标识做枚举管理(避免硬编码) :将所有节点标识(如applyInformationFirstbankInsuranceBeginAudit)定义为枚举类(如AuditNodeEnum),前端调用时传入枚举值,后端工厂类和接口层使用枚举类获取标识,避免硬编码带来的拼写错误,提升代码可维护性;
  3. 增加入参类型校验(健壮性优化) :在策略类的validate方法中,增加入参类型的校验(如if (!(params[0] instanceof InformationDTO)) { throw new ServiceException("入参类型错误"); }),避免前端传入错误的入参类型导致类型转换异常;
  4. 支持节点优先级和流程终止 :在策略类中增加getPriority()方法,定义节点执行优先级,若高优先级节点校验失败/执行异常,可直接终止后续节点执行,适配「多节点串联审核」的业务场景;
  5. 增加策略类执行监控(可观测性优化) :通过AOP切面围绕INodeAuditHandlervalidateexecute方法,记录每个节点的执行耗时、调用次数、成功次数、失败次数、异常次数,对接监控平台(如Prometheus+Grafana),便于后续性能优化和问题排查;
  6. 支持策略类组合(功能扩展) :新增「组合策略类」,实现INodeAuditHandler接口,内部封装多个基础策略类,支持「与/或」逻辑(如多个节点同时校验通过才允许执行下一步),适配复杂的审核业务场景;
  7. 增加重试机制(容错性优化):对非致命异常的节点执行(如第三方接口调用超时、数据库临时连接失败),增加重试机制(如基于Spring Retry),提升系统的容错性和稳定性;
  8. 接口版本控制(兼容升级) :若后续业务需要对节点逻辑做兼容升级,可在通用接口中增加版本控制(如/api/v1/node/audit/{handlerName}/api/v2/node/audit/{handlerName}),避免版本升级影响原有业务。

六、面试话术参考(直接用,体现专业性)

当面试官问到「你在项目中是如何实现多业务场景的单接口适配?」「谈谈你对策略模式+工厂模式的理解和实践?」时,可按以下话术回答,逻辑清晰、结合业务、突出亮点:

在我们的进件审核项目中,存在多个独立的审核节点(如进件第一步、保银初审、GPS安装审核等),原有方案是一个节点对应一个接口,存在代码冗余、可扩展性差的问题。因此我采用了策略模式+工厂模式的组合方案,基于Spring原生能力实现了「单接口适配多审核节点」的需求。

首先,定义了INodeAuditHandler作为策略接口 ,规范了所有审核节点的validateexecute方法;然后将每个审核节点的业务逻辑封装为独立的策略类 ,实现该接口并通过@Component标注唯一的Bean名称,保证逻辑的解耦和独立;接着开发了NodeAuditHandlerFactory工厂类 ,利用Spring自动注入的Map管理所有策略类,提供按节点标识获取策略类的方法,封装了对象创建逻辑;最后写了一个通用接口,从URL中获取节点标识,通过工厂类动态获取策略类,统一执行「校验→执行」的流程。

这套方案的核心优势是符合开闭原则 ,新增审核节点仅需新增策略类,无需修改原有代码;同时架构解耦性强,接口层、工厂层、策略层各司其职,便于团队维护;另外,完全基于Spring原生能力实现,无第三方依赖,降低了项目复杂度。

在实现过程中,需要注意策略类Bean名称的唯一性、Spring Map的自动注入规则,避免出现Bean冲突和注入失败的问题;后续还可以通过枚举管理节点标识、AOP实现执行监控、增加重试机制等方式,让架构更健壮、功能更完善。


以上内容从设计模式定义、代码实现、请求流程、面试考点四个维度做了全方面解析,你可以直接基于这份内容落地代码,也能快速掌握面试中关于该方案的核心考点,做到全方面理解这种写法。

相关推荐
lee_curry2 分钟前
jvm中的内存模型
java·jvm·内存模型
tltwuyulw4 分钟前
Java的函数式编程(三)
java·后端
ch.ju4 分钟前
Java程序设计(第3版)第二章——嵌套循环
java
直奔標竿4 分钟前
Java开发者AI转型第九课!突破知识边界!企业级 RAG (检索增强生成) 核心架构与 ETL 管道初探
java·开发语言·人工智能·后端·spring
skilllite作者7 分钟前
SkillLite Rust 沙箱与 AI Agent 自进化实战指南
开发语言·人工智能·后端·架构·rust
Java女侠_9年实战9 分钟前
为什么会丢精度?BigDecimal正确用法
后端
程途知微9 分钟前
ThreadLocal底层原理
java·后端
宝耶11 分钟前
[特殊字符] 操作日志模块复习笔记
java·开发语言·jvm
好好研究12 分钟前
Java基础学习(十三):IO流基础
java·开发语言·学习·io流
wuxinyan12313 分钟前
Java面试题52:一文深入了解Kubernetes 核心资源对象
java·kubernetes·面试题