在复杂项目开发中设计模式是解决共性问题、提升代码可维护性、可扩展性的核心工具。脱离业务场景的设计模式只是“纸上谈兵”而Ragent项目中7种设计模式的应用的则完美诠释了“模式服务于业务”的核心思想——每个模式都对应具体的业务痛点每个实现都有完整的源码支撑可直接复用、可扩展、可测试。本文将基于Ragent项目源码逐一对7种设计模式的应用场景、核心实现、源码解析和落地价值进行深度拆解带你看懂设计模式如何解决实际开发中的问题以及如何在自己的项目中借鉴这些实践。一、设计模式总览7种模式覆盖核心业务场景Ragent项目中的7种设计模式分别对应检索、回调、结果处理、组件管理等核心业务场景每种模式都有明确的应用目标和核心价值形成了一套完整的“模式应用体系”。先通过一张表格快速总览序号模式名称应用场景核心价值核心组件1策略模式 (Strategy)多通道检索可插拔的检索算法灵活切换检索方式SearchChannel接口、多通道实现类2工厂模式 (Factory)StreamCallback 创建封装复杂对象创建过程统一创建入口StreamCallbackFactory3装饰器模式 (Decorator)首包探测缓冲动态增强对象功能不修改原有代码ProbeBufferingCallback4责任链模式 (Chain of Responsibility)后置处理器链按序处理请求解耦处理器与调用者SearchResultPostProcessor接口、多处理器实现5注册表模式 (Registry)自动发现通道/处理器实现插件化架构新增组件无需修改核心代码Spring自动注入的List6模板方法模式 (Template Method)文本分块策略、并行检索复用算法骨架延迟具体实现到子类AbstractParallelRetriever抽象类7建造者模式 (Builder)实体对象构建链式构造复杂对象提升代码可读性IntentNode、ChatRequest等带Builder注解的实体这些模式并非孤立存在在实际业务中常常组合使用比如多通道检索模块就同时用到了注册表模式、策略模式和模板方法模式形成了“插件化、可扩展、可复用”的检索体系。接下来我们逐一对每种模式进行源码级解析。二、策略模式多通道检索的“可插拔”实现2.1 模式核心定义策略模式的核心是“定义一系列算法将其封装起来并且使它们可以相互替换”。它的核心价值在于解耦算法的定义与使用使得算法可以独立于使用它的客户端而变化。在Ragent项目中多通道检索是核心业务之一——需要支持意图定向检索、向量全局检索、未来可扩展的ES关键词检索等多种检索方式且每种检索方式的启用条件、优先级、实现逻辑都不同这正是策略模式的典型应用场景。2.2 源码解析策略接口与实现首先定义检索策略的统一接口SearchChannel所有检索通道都需实现该接口规范检索策略的核心方法// bootstrap/src/main/java/com/nageoffer/ai/ragent/rag/core/retrieve/channel/SearchChannel.java /** * 检索通道接口策略接口 * 每个通道负责一种检索策略统一规范检索方法 */ public interface SearchChannel { /** 通道名称用于日志和标识 */ String getName(); /** 通道优先级数字越小优先级越高用于排序执行 */ int getPriority(); /** 是否启用该通道根据检索上下文动态判断 */ boolean isEnabled(SearchContext context); /** 执行检索核心策略实现方法 */ SearchChannelResult search(SearchContext context); /** 通道类型区分不同检索类型如意图定向、向量全局 */ SearchChannelType getType(); }接口定义了检索策略的核心契约名称、优先级、启用条件、检索逻辑和类型确保所有检索策略都遵循统一规范。接下来实现两种核心检索策略2.2.1 意图定向检索策略该策略优先级最高priority1仅在有KB意图时启用核心逻辑是根据用户意图在指定的Collection中并行检索提升检索精准度// IntentDirectedSearchChannel.java Slf4j Component public class IntentDirectedSearchChannel implements SearchChannel { // 注入并行检索器后续会用到模板方法模式 private final ParallelRetriever parallelRetriever; Override public String getName() { return IntentDirectedSearch; // 策略名称 } Override public int getPriority() { return 1; // 最高优先级优先执行 } Override public boolean isEnabled(SearchContext context) { // 策略启用条件提取到KB意图时才启用 ListNodeScore kbIntents extractKbIntents(context); return CollUtil.isNotEmpty(kbIntents); } Override public SearchChannelResult search(SearchContext context) { // 1. 提取用户的KB意图核心业务逻辑 ListNodeScore kbIntents extractKbIntents(context); // 2. 并行在每个意图对应的Collection中检索复用并行检索逻辑 MapString, ListRetrievedChunk results parallelRetriever.retrieve(kbIntents, context); // 3. 合并多Collection的检索结果返回统一格式 return mergeResults(results); } // 辅助方法提取KB意图省略具体实现 private ListNodeScore extractKbIntents(SearchContext context) { // ... 从上下文提取用户意图筛选KB相关意图 } // 辅助方法合并检索结果省略具体实现 private SearchChannelResult mergeResults(MapString, ListRetrievedChunk results) { // ... 合并、去重、排序返回统一的SearchChannelResult } }2.2.2 向量全局检索策略该策略优先级较低priority10在没有意图或意图置信度过低时启用核心逻辑是在所有Collection中进行全局向量检索确保检索的全面性// VectorGlobalSearchChannel.java Slf4j Component public class VectorGlobalSearchChannel implements SearchChannel { // 注入知识库Mapper用于获取所有Collection private final KnowledgeBaseMapper knowledgeBaseMapper; // 意图置信度阈值低于该阈值则启用全局检索 private final double confidenceThreshold 0.5; Override public String getName() { return VectorGlobalSearch; } Override public int getPriority() { return 10; // 较低优先级意图检索失败后执行 } Override public boolean isEnabled(SearchContext context) { // 启用条件1完全没有意图 if (context.getIntents().isEmpty()) { return true; } // 启用条件2所有意图的置信度都低于阈值 double maxScore getMaxIntentScore(context); return maxScore confidenceThreshold; } Override public SearchChannelResult search(SearchContext context) { // 核心逻辑获取所有Collection执行全局检索 ListString collections knowledgeBaseMapper.getAllCollections(); return parallelRetriever.retrieveAll(collections, context); } // 辅助方法获取意图的最高置信度省略具体实现 private double getMaxIntentScore(SearchContext context) { // ... 遍历意图列表返回最高置信度 } }2.3 策略选择与执行多通道并行调度策略模式的关键的是“策略选择”Ragent项目中通过MultiChannelRetrievalEngine实现策略的筛选、排序和并行执行无需手动判断使用哪种策略// MultiChannelRetrievalEngine.java Service RequiredArgsConstructor public class MultiChannelRetrievalEngine { // 注册表模式Spring自动注入所有SearchChannel实现后续详解 private final ListSearchChannel searchChannels; // 线程池用于并行执行检索通道 private final ExecutorService ragRetrievalExecutor; public ListSearchChannelResult executeSearchChannels(SearchContext context) { // 1. 筛选启用的通道根据isEnabled()判断按优先级排序 ListSearchChannel enabledChannels searchChannels.stream() .filter(channel - channel.isEnabled(context)) .sorted(Comparator.comparingInt(SearchChannel::getPriority)) .toList(); // 2. 并行执行所有启用的通道提升检索效率 ListCompletableFutureSearchChannelResult futures enabledChannels.stream() .map(channel - CompletableFuture.supplyAsync( () - channel.search(context), // 执行具体策略 ragRetrievalExecutor )) .toList(); // 3. 等待所有并行任务完成返回结果列表 return futures.stream() .map(CompletableFuture::join) .toList(); } }2.4 策略模式的落地价值结合Ragent的源码实践策略模式带来了4个核心价值完美解决了多通道检索的痛点可扩展性新增检索策略如ES关键词检索只需实现SearchChannel接口添加Component注解无需修改核心调度代码实现“插件化”扩展。可配置性每个策略通过isEnabled()方法动态控制启用状态通过getPriority()控制执行顺序灵活适配不同业务场景。可测试性每个检索策略独立封装可单独编写单元测试无需依赖其他策略降低测试难度。解耦性检索策略的实现与调度逻辑分离调度器MultiChannelRetrievalEngine只需依赖SearchChannel接口无需关心具体策略的实现细节。三、工厂模式复杂对象创建的“封装者”3.1 模式核心定义工厂模式的核心是“封装对象的创建过程根据参数决定创建哪种类型的对象”。当一个对象的创建过程复杂依赖多、参数多时工厂模式可以隐藏创建细节提供统一的创建入口降低客户端的使用成本。在Ragent项目中StreamCallback流式回调的创建过程非常复杂需要依赖多个服务会话记忆、任务管理等和配置因此使用工厂模式封装其创建逻辑。3.2 源码解析StreamCallback工厂首先StreamChatEventHandlerStreamCallback的具体实现的构造需要7个依赖参数直接在客户端创建会导致代码冗余、耦合度高因此创建StreamCallbackFactory统一封装创建过程// bootstrap/src/main/java/com/nageoffer/ai/ragent/rag/service/handler/StreamCallbackFactory.java Component RequiredArgsConstructor public class StreamCallbackFactory { // 依赖的服务和配置共6个创建复杂 private final AIModelProperties modelProperties; private final ConversationMemoryService memoryService; private final ConversationGroupService conversationGroupService; private final StreamTaskManager taskManager; /** * 工厂核心方法创建聊天事件处理器StreamCallback * 客户端只需传入3个关键参数无需关心内部依赖 */ public StreamCallback createChatEventHandler(SseEmitter emitter, String conversationId, String taskId) { // 1. 使用建造者模式构建参数对象后续详解建造者模式 StreamChatHandlerParams params StreamChatHandlerParams.builder() .emitter(emitter) .conversationId(conversationId) .taskId(taskId) .modelProperties(modelProperties) .memoryService(memoryService) .conversationGroupService(conversationGroupService) .taskManager(taskManager) .build(); // 2. 封装创建逻辑返回具体的StreamCallback实现 return new StreamChatEventHandler(params); } }3.3 工厂模式的使用场景客户端如RAGChatController在需要创建StreamCallback时只需调用工厂的方法传入必要参数即可无需关心内部依赖的注入和参数的组装// RAGChatController.java RestController RequestMapping(/rag) RequiredArgsConstructor public class RAGChatController { private final StreamCallbackFactory streamCallbackFactory; private final LLMService llmService; // 流式聊天接口 GetMapping(value /v3/chat, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter chat(RequestParam String question, RequestParam(required false) String conversationId) { // 1. 创建SseEmitter服务端向客户端推送流数据 SseEmitter emitter new SseEmitter(30 * 60 * 1000L); // 30分钟超时 // 2. 生成任务ID用于任务管理 String taskId UUID.randomUUID().toString(); // 3. 通过工厂创建StreamCallback无需关心内部依赖 StreamCallback callback streamCallbackFactory.createChatEventHandler( emitter, conversationId, taskId ); // 4. 发起流式请求传入回调 ChatRequest request ChatRequest.builder().question(question).build(); llmService.streamChat(request, callback); return emitter; } }3.4 工厂模式的落地价值封装复杂性隐藏StreamChatEventHandler的复杂创建过程客户端无需关心其依赖的6个服务只需传入3个关键参数降低使用成本。统一创建入口所有StreamCallback都通过工厂创建便于后续统一修改创建逻辑如新增依赖、调整参数无需修改所有客户端代码。易于扩展如果后续需要创建其他类型的StreamCallback如日志回调、统计回调只需在工厂中新增方法客户端无需改动符合“开闭原则”。四、装饰器模式动态增强对象功能的“魔法”4.1 模式核心定义装饰器模式的核心是“动态地给对象添加一些额外的职责比继承更灵活”。它通过“包装”原有对象在不修改原有对象代码的前提下增强其功能且可以叠加多个装饰器实现功能的组合。在Ragent项目中流式调用的首包探测场景需要增强StreamCallback的功能——在首包探测阶段缓存所有输出事件避免失败模型的内容污染下游输出首包成功后再回放缓存的事件这正是装饰器模式的典型应用。4.2 源码解析首包探测缓冲装饰器Ragent项目中ProbeBufferingCallback作为装饰器包装了原始的StreamCallback新增了“首包探测”和“事件缓冲”功能且不修改原始回调的代码// RoutingLLMService.java 内部类装饰器实现 /** * 探测缓冲回调装饰器 * 核心功能 * 1. 首包探测阶段缓存所有事件避免失败模型的内容污染下游 * 2. 首包成功后commit() 回放缓存转实时转发 */ private static final class ProbeBufferingCallback implements StreamCallback { private final StreamCallback downstream; // 被装饰的原始回调核心对象 private final FirstPacketAwaiter awaiter; // 首包探测器新增功能依赖 private final ListBufferedEvent bufferedEvents new ArrayList(); // 事件缓存 private volatile boolean committed; // 标记是否已提交首包是否成功 private final Object lock new Object(); // 线程安全锁 // 构造方法传入被装饰的原始回调和探测器 ProbeBufferingCallback(StreamCallback downstream, FirstPacketAwaiter awaiter) { this.downstream downstream; // 保存原有回调后续委托调用 this.awaiter awaiter; this.committed false; } // 增强onContent方法缓存或转发内容 Override public void onContent(String content) { awaiter.markContent(); // 新增功能标记收到首包内容 bufferOrDispatch(BufferedEvent.content(content)); // 缓存或转发 } // 增强onThinking方法缓存或转发思考过程 Override public void onThinking(String content) { awaiter.markContent(); bufferOrDispatch(BufferedEvent.thinking(content)); } // 增强onComplete方法缓存或转发完成事件 Override public void onComplete() { awaiter.markComplete(); bufferOrDispatch(BufferedEvent.complete()); } // 核心逻辑判断是缓存事件还是直接转发 private void bufferOrDispatch(BufferedEvent event) { synchronized (lock) { if (committed) { // 首包成功已提交直接转发给下游委托原始回调 dispatch(event); } else { // 首包未成功缓存事件避免污染下游 bufferedEvents.add(event); } } } // 新增方法首包成功后回放缓存的事件 void commit() { synchronized (lock) { committed true; // 按顺序回放缓存的事件转发给下游 for (BufferedEvent event : bufferedEvents) { dispatch(event); } bufferedEvents.clear(); // 清空缓存释放资源 } } // 辅助方法转发事件到原始回调 private void dispatch(BufferedEvent event) { switch (event.type) { case CONTENT - downstream.onContent(event.content); case THINKING - downstream.onThinking(event.content); case COMPLETE - downstream.onComplete(); } } // 内部类封装缓冲的事件内容、思考、完成 private static class BufferedEvent { private final Type type; private final String content; private BufferedEvent(Type type, String content) { this.type type; this.content content; } public static BufferedEvent content(String content) { return new BufferedEvent(Type.CONTENT, content); } public static BufferedEvent thinking(String content) { return new BufferedEvent(Type.THINKING, content); } public static BufferedEvent complete() { return new BufferedEvent(Type.COMPLETE, null); } private enum Type { CONTENT, THINKING, COMPLETE } } }4.3 装饰器模式的使用场景在RoutingLLMService的流式调用方法中使用ProbeBufferingCallback包装原始StreamCallback实现首包探测和事件缓冲功能// RoutingLLMService.streamChat() Override public StreamCancellationHandle streamChat(ChatRequest request, StreamCallback callback) { // 1. 获取候选模型列表策略模式注册表模式 ListModelTarget targets selector.selectChatCandidates(request.getThinking()); if (CollUtil.isEmpty(targets)) { throw new RemoteException(无可用模型); } // 2. 遍历候选模型尝试流式调用 for (ModelTarget target : targets) { ChatClient client resolveClient(target); if (client null) continue; // 3. 创建首包探测器 FirstPacketAwaiter awaiter new FirstPacketAwaiter(); // 4. 用装饰器包装原始回调增强首包探测和缓冲功能 ProbeBufferingCallback wrapper new ProbeBufferingCallback(callback, awaiter); // 5. 发起流式请求传入装饰后的回调 StreamCancellationHandle handle client.streamChat(request, wrapper, target); // 6. 等待首包60秒超时 FirstPacketAwaiter.Result result awaiter.await(60, TimeUnit.SECONDS); if (result.isSuccess()) { wrapper.commit(); // 首包成功回放缓存事件 return handle; // 返回调用句柄后续内容实时转发 } // 首包失败取消请求缓存的事件被丢弃未commit handle.cancel(); healthStore.markFailure(target.id()); // 标记模型失败 } // 所有模型都失败抛出异常 throw new RemoteException(所有模型调用失败); }4.4 装饰器模式的落地价值动态增强在运行时给StreamCallback新增首包探测和事件缓冲功能无需修改原始回调的代码符合“开闭原则”。无侵入性装饰器通过“委托”方式调用原始对象的方法不改变原始对象的结构和逻辑降低耦合度。可叠加性如果后续需要新增其他功能如日志记录、耗时统计只需再创建一个装饰器包装在ProbeBufferingCallback外层实现功能组合。失败保护首包探测失败时缓存的事件不会被转发避免失败模型的错误内容污染下游提升用户体验。五、责任链模式后置处理器的“按序执行”机制5.1 模式核心定义责任链模式的核心是“将请求沿着处理者链传递直到有一个处理者处理它”。它的核心价值是解耦请求的发送者和处理者让多个处理者可以按顺序处理请求且可以灵活调整处理者的顺序和数量。在Ragent项目中多通道检索的结果需要经过一系列后处理去重、排序、过滤等每个处理步骤独立且需要按顺序执行这正是责任链模式的应用场景。5.2 源码解析处理器接口与实现首先定义后置处理器的统一接口SearchResultPostProcessor规范处理方法和排序规则// bootstrap/src/main/java/com/nageoffer/ai/ragent/rag/core/retrieve/postprocessor/SearchResultPostProcessor.java /** * 检索结果后置处理器接口责任链节点接口 * 对多通道检索结果进行统一后处理如去重、排序、过滤等 */ public interface SearchResultPostProcessor { /** 处理器名称用于日志和标识 */ String getName(); /** 处理器优先级数字越小越先执行用于排序形成责任链 */ int getOrder(); /** 是否启用该处理器根据检索上下文动态判断 */ boolean isEnabled(SearchContext context); /** * 核心处理方法接收上一个处理器的输出处理后传递给下一个处理器 * param chunks 当前的Chunk列表上一个处理器的输出 * param results 原始的多通道检索结果 * param context 检索上下文 * return 处理后的Chunk列表传递给下一个处理器 */ ListRetrievedChunk process(ListRetrievedChunk chunks, ListSearchChannelResult results, SearchContext context); }接口定义了责任链节点的核心契约名称、执行顺序、启用条件和处理逻辑确保每个处理器都遵循统一规范。接下来实现两个核心处理器5.2.1 去重处理器第一个执行该处理器优先级最高order1负责去除检索结果中完全相同的Chunk避免重复内容影响后续处理// DeduplicationPostProcessor.java Slf4j Component public class DeduplicationPostProcessor implements SearchResultPostProcessor { Override public String getName() { return Deduplication; // 处理器名称去重 } Override public int getOrder() { return 1; // 第一个执行先去重再进行其他处理 } Override public boolean isEnabled(SearchContext context) { return true; // 始终启用所有检索结果都需要去重 } Override public ListRetrievedChunk process(ListRetrievedChunk chunks, ListSearchChannelResult results, SearchContext context) { // 核心逻辑去除完全相同的Chunk依赖RetrievedChunk的equals和hashCode方法 int beforeSize chunks.size(); ListRetrievedChunk deduplicatedChunks chunks.stream() .distinct() .collect(Collectors.toList()); log.info(去重处理器完成 - 输入{}个Chunk输出{}个Chunk, beforeSize, deduplicatedChunks.size()); return deduplicatedChunks; } }5.2.2 Rerank排序处理器最后执行该处理器优先级较低order10负责对去重后的Chunk进行相关性重新排序提升检索结果的精准度// RerankPostProcessor.java Slf4j Component RequiredArgsConstructor public class RerankPostProcessor implements SearchResultPostProcessor { private final RerankService rerankService; // 注入Rerank排序服务 Override public String getName() { return Rerank; // 处理器名称重新排序 } Override public int getOrder() { return 10; // 最后执行排序是最终处理步骤 } Override public boolean isEnabled(SearchContext context) { return true; // 始终启用所有检索结果都需要排序 } Override public ListRetrievedChunk process(ListRetrievedChunk chunks, ListSearchChannelResult results, SearchContext context) { if (chunks.isEmpty()) { log.info(Rerank处理器输入Chunk为空直接返回); return chunks; } // 核心逻辑调用Rerank模型根据用户问题重新排序 ListRetrievedChunk rerankedChunks rerankService.rerank( context.getMainQuestion(), // 用户主问题 chunks, // 去重后的Chunk列表 context.getTopK() // 需要返回的TopK数量 ); log.info(Rerank处理器完成 - 排序后Chunk数量{}, rerankedChunks.size()); return rerankedChunks; } }5.3 责任链执行按序传递处理在MultiChannelRetrievalEngine中将所有启用的处理器按优先级排序形成责任链依次执行处理逻辑将上一个处理器的输出作为下一个处理器的输入// MultiChannelRetrievalEngine.java Service RequiredArgsConstructor public class MultiChannelRetrievalEngine { // 注册表模式Spring自动注入所有后置处理器 private final ListSearchResultPostProcessor postProcessors; /** * 执行后置处理器链按order排序依次处理检索结果 */ private ListRetrievedChunk executePostProcessors( ListSearchChannelResult results, SearchContext context) { // 1. 初始Chunk列表合并所有检索通道的结果 ListRetrievedChunk chunks results.stream() .flatMap(r - r.getChunks().stream()) .collect(Collectors.toList()); log.info(后置处理器链开始 - 初始Chunk数量{}, chunks.size()); // 2. 筛选启用的处理器按order排序形成责任链 ListSearchResultPostProcessor enabledProcessors postProcessors.stream() .filter(processor - processor.isEnabled(context)) .sorted(Comparator.comparingInt(SearchResultPostProcessor::getOrder)) .toList(); // 3. 执行责任链依次处理传递结果 for (SearchResultPostProcessor processor : enabledProcessors) { int beforeSize chunks.size(); // 上一个处理器的输出 → 当前处理器的输入 chunks processor.process(chunks, results, context); int afterSize chunks.size(); log.info(处理器 {} 完成 - 输入{}输出{}, processor.getName(), beforeSize, afterSize); } return chunks; } }5.4 责任链模式的落地价值解耦性每个处理器独立封装只关注自己的处理逻辑不关心上一个处理器的输入和下一个处理器的输出解耦处理器与调用者。灵活性可以随时新增、删除处理器或调整处理器的执行顺序修改order值无需修改核心执行代码。可扩展性新增后处理逻辑如关键词过滤、权限校验只需实现SearchResultPostProcessor接口添加Component注解即可自动加入责任链。可测试性每个处理器独立可单独编写单元测试验证其处理逻辑的正确性降低测试难度。六、注册表模式组件自动发现的“插件化”基石6.1 模式核心定义注册表模式的核心是“通过注册表自动收集和存储组件实例便于查找和使用”。它的核心价值是实现组件的自动发现和管理无需手动注册组件降低组件集成的成本实现插件化架构。在Ragent项目中检索通道SearchChannel和后置处理器SearchResultPostProcessor的数量可能会不断扩展手动注册每个组件会导致代码冗余、维护成本高因此使用注册表模式借助Spring的自动注入功能实现组件的自动发现。6.2 源码解析Spring自动注入实现注册表Ragent项目中注册表模式的实现非常简洁借助Spring的Component注解和List注入功能自动收集所有实现了指定接口的组件形成注册表// MultiChannelRetrievalEngine.java Service RequiredArgsConstructor public class MultiChannelRetrievalEngine { // 注册表模式核心Spring自动注入所有SearchChannel实现 // 相当于一个“检索通道注册表”新增通道无需手动注册 private final ListSearchChannel searchChannels; // 注册表模式核心Spring自动注入所有SearchResultPostProcessor实现 // 相当于一个“后置处理器注册表”新增处理器无需手动注册 private final ListSearchResultPostProcessor postProcessors; // ... 其他方法策略选择、责任链执行等 }6.3 原理揭秘Spring如何实现自动注册注册表模式的实现依赖Spring的组件扫描和依赖注入机制具体流程如下Spring启动时会扫描项目中所有带有Component及其衍生注解如Service、Controller的类。对于实现了SearchChannel接口的类如IntentDirectedSearchChannel、VectorGlobalSearchChannelSpring会自动创建其实例并将所有实例收集到ListSearchChannel中。MultiChannelRetrievalEngine通过构造方法注入ListSearchChannel即可获取所有检索通道实例无需手动注册任何通道。新增检索通道时只需创建类实现SearchChannel接口添加Component注解Spring会自动将其加入注册表MultiChannelRetrievalEngine无需任何修改。6.4 扩展示例新增ES检索通道借助注册表模式新增一个ES关键词检索通道只需3步无需修改核心代码// ESSearchChannel.java Component // 1. 添加Component注解Spring自动扫描注册 public class ESSearchChannel implements SearchChannel { // 2. 实现SearchChannel接口 // 注入ES客户端省略 private final RestHighLevelClient esClient; Override public String getName() { return ElasticsearchKeywordSearch; } Override public int getPriority() { return 5; // 优先级介于意图检索和全局检索之间 } Override public boolean isEnabled(SearchContext context) { // 自定义启用条件配置启用且问题包含关键词 return properties.isEsSearchEnabled() containsKeyword(context.getMainQuestion()); } Override public SearchChannelResult search(SearchContext context) { // 3. 实现ES关键词检索逻辑省略 return esClient.search(context.getMainQuestion(), context.getTopK()); } // 辅助方法判断问题是否包含关键词省略 private boolean containsKeyword(String question) { // ... } }运行结果Spring启动时会自动将ESSearchChannel实例加入ListSearchChannelMultiChannelRetrievalEngine会自动筛选、排序并执行该通道无需修改任何核心代码。6.5 注册表模式的落地价值插件化架构新增组件检索通道、处理器只需实现接口、添加注解无需修改核心代码实现“即插即用”。自动管理Spring自动完成组件的创建、注入和管理减少手动注册的冗余代码降低维护成本。动态发现运行时可以根据配置启用/禁用组件组件的新增、删除不影响核心逻辑提升系统的可扩展性。降低耦合核心模块如MultiChannelRetrievalEngine无需依赖具体的组件实现只需依赖接口降低耦合度。七、模板方法模式算法骨架复用的“高效工具”7.1 模式核心定义模板方法模式的核心是“定义算法骨架将某些步骤延迟到子类中实现”。它的核心价值是复用算法的公共逻辑将可变的具体实现延迟到子类提升代码复用性同时保证算法的结构一致。在Ragent项目中并行检索是一个通用场景——无论是意图定向检索还是集合并行检索其核心流程获取目标、并行执行、合并结果都是相同的只有“获取目标”和“单个目标检索”这两个步骤不同因此使用模板方法模式封装公共逻辑。7.2 源码解析抽象模板类定义抽象基类AbstractParallelRetriever封装并行检索的核心骨架模板方法将可变步骤定义为抽象方法延迟到子类实现// bootstrap/src/main/java/com/nageoffer/ai/ragent/rag/core/retrieve/channel/strategy/AbstractParallelRetriever.java /** * 并行检索器抽象基类模板类 * 定义并行检索的完整算法骨架可变步骤延迟到子类实现 */ public abstract class AbstractParallelRetriever { // 公共依赖检索服务和线程池所有子类共享 private final RetrieverService retrieverService; private final Executor executor; // 构造方法注入公共依赖子类通过super调用 public AbstractParallelRetriever(RetrieverService retrieverService, Executor executor) { this.retrieverService retrieverService; this.executor executor; } /** * 模板方法定义并行检索的完整流程算法骨架 * 步骤固定获取目标 → 并行执行 → 合并结果 */ public MapString, ListRetrievedChunk retrieve(SearchContext context) { // Step 1: 获取要检索的目标可变步骤子类实现 ListString targets getTargets(context); if (targets.isEmpty()) { log.info(并行检索无检索目标返回空结果); return Map.of();