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

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

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实现执行监控、增加重试机制等方式,让架构更健壮、功能更完善。


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

相关推荐
csdn_aspnet2 小时前
ASP.NET 8 - Cookie 身份验证
后端·asp.net·cookie·.net8
坚持就完事了2 小时前
Java的OOP
java·开发语言
笔画人生2 小时前
Cursor + 蓝耘API:用自然语言完成全栈项目开发
前端·后端
像少年啦飞驰点、2 小时前
零基础入门 Spring Boot:从“Hello World”到可部署微服务的完整学习路径
java·spring boot·微服务·编程入门·后端开发
undsky_3 小时前
【RuoYi-SpringBoot3-Pro】:将 AI 编程融入传统 java 开发
java·人工智能·spring boot·ai·ai编程
不光头强3 小时前
shiro学习要点
java·学习·spring
HAPPY酷3 小时前
构建即自由:一份为创造者设计的 Windows C++ 自动化构建指南
开发语言·c++·ide·windows·python·策略模式·visual studio
工一木子3 小时前
Java 的前世今生:从 Oak 到现代企业级语言
java·开发语言
H Journey3 小时前
Linux su 命令核心用法总结
java·linux·服务器·su