ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

软件工厂设计模式:从工厂模式到可组合部件与Agent编排

软件工厂设计模式:从工厂模式到可组合部件与Agent编排 1. 从“软件工厂”说起为什么我们需要可组合的构建方式先聊一个很多团队都会遇到的问题当业务规模逐渐扩大代码仓库从几个类膨胀到上百个模块新功能上线越来越慢重复代码越来越多不同开发者的实现风格也千差万别。此时你往往会听到一种声音——“我们的软件生产应该像工厂流水线一样标准化”。这正是“软件工厂”这个概念的出发点。它并不是说要建一个物理上的工厂而是一种软件构建的方法论把软件系统拆分成标准化的部件通过定义好的接口和流程进行装配最终像流水线一样稳定地产出可交付的功能。把这个思路落到技术层面就绕不开两个关键词设计模式和可组合部件。设计模式提供了经过验证的、可复用的解决方案模板它告诉我们在特定场景下应该怎么组织类和对象。可组合部件则强调系统由多个独立、可替换、可复用的模块拼装而成而不是一棵难以拆分的“大泥球”。两者结合就是我们今天要聊的主题软件工厂设计模式。它不特指某一个设计模式而是一套用“工厂思维 组合思维”来构建软件系统的方法论。本文会从经典设计模式中的工厂模式讲起逐步过渡到可组合部件设计最后给出一个完整的代码示例帮助你把这套思路落地到实际项目中。2. 设计模式里的“工厂”从 Factory Method 到 Abstract Factory2.1 为什么要用工厂模式想象这样一个场景你的业务系统需要对接多家短信服务商有阿里云短信、腾讯云短信、也有自研的短信网关。如果直接在业务代码里写死“如果配置是阿里云就 new 阿里云短信客户端如果配置是腾讯云就 new 腾讯云短信客户端”那么每一次新增服务商都要修改调用方代码这违反了开闭原则对扩展开放对修改关闭。工厂模式的解决思路很直接把“创建对象的逻辑”从调用方剥离出来交给一个专门的工厂负责。调用方只需要告诉工厂“我要什么类型的短信客户端”工厂负责决定实例化哪个具体类。2.2 工厂方法模式让子类决定创建哪个对象工厂方法模式定义了一个创建对象的接口但由子类决定要实例化哪一个类。换句话说工厂方法让一个类的实例化延迟到其子类。先来看一个最小示例。假设我们有一个短信发送器的抽象接口// 文件路径com.example.sms.SmsSender.java public interface SmsSender { void send(String phone, String content); }两个具体实现// 文件路径com.example.sms.AliyunSmsSender.java public class AliyunSmsSender implements SmsSender { Override public void send(String phone, String content) { System.out.println([阿里云短信] 发送给 phone content); } }// 文件路径com.example.sms.TencentSmsSender.java public class TencentSmsSender implements SmsSender { Override public void send(String phone, String content) { System.out.println([腾讯云短信] 发送给 phone content); } }接下来定义工厂接口和两个具体工厂// 文件路径com.example.factory.SmsSenderFactory.java public interface SmsSenderFactory { SmsSender createSmsSender(); }// 文件路径com.example.factory.AliyunSmsSenderFactory.java public class AliyunSmsSenderFactory implements SmsSenderFactory { Override public SmsSender createSmsSender() { return new AliyunSmsSender(); } }// 文件路径com.example.factory.TencentSmsSenderFactory.java public class TencentSmsSenderFactory implements SmsSenderFactory { Override public SmsSender createSmsSender() { return new TencentSmsSender(); } }调用方代码如下// 文件路径com.example.Client.java public class Client { public static void main(String[] args) { SmsSenderFactory factory new AliyunSmsSenderFactory(); SmsSender sender factory.createSmsSender(); sender.send(13800138000, 您的验证码是 123456); } }这段代码的关键点是Client只依赖SmsSender接口和SmsSenderFactory接口不依赖任何具体实现类。将来新增一家短信服务商只需要新增一个实现类和对应的工厂类不需要改动Client。2.3 抽象工厂模式创建一系列相关对象工厂方法模式解决的是“单个产品”的创建问题。但现实中我们往往需要创建的是一个“产品族”。比如一个跨平台的 UI 组件库Windows 风格和 Mac 风格各自拥有一整套按钮、输入框、弹窗等组件这些组件之间需要保持一致的外观和交互逻辑。抽象工厂模式就是为这种情况设计的它提供一个接口用于创建一系列相关或相互依赖的对象而无需指定它们的具体类。// 文件路径com.example.ui.Button.java public interface Button { void render(); }// 文件路径com.example.ui.WindowButton.java public class WindowButton implements Button { Override public void render() { System.out.println(渲染 Windows 风格按钮); } }// 文件路径com.example.ui.MacButton.java public class MacButton implements Button { Override public void render() { System.out.println(渲染 Mac 风格按钮); } }然后定义 UI 工厂接口和两个实现// 文件路径com.example.ui.UiFactory.java public interface UiFactory { Button createButton(); // 假设还有 createInput()、createDialog() 等 }// 文件路径com.example.ui.WindowsFactory.java public class WindowsFactory implements UiFactory { Override public Button createButton() { return new WindowButton(); } }// 文件路径com.example.ui.MacFactory.java public class MacFactory implements UiFactory { Override public Button createButton() { return new MacButton(); } }使用抽象工厂模式后客户端代码只需要在启动时决定“当前系统使用哪一套工厂”后续创建组件时不必关心具体风格。3. 从“工厂”到“可组合部件”设计思想的进阶3.1 工厂模式解决了什么问题还有什么问题工厂模式解决的核心问题是创建逻辑与使用逻辑的解耦。它让系统在“创建对象”这个环节具备了扩展性。但工厂模式本身并不关心“创建出来的对象如何使用、如何组装”。举一个实际案例你通过工厂创建了一个短信发送器但业务上还需要对短信内容做敏感词过滤、需要在发送前记录日志、需要支持失败重试。这些功能如果都堆在SmsSender的实现类里很快就会变成一个大杂烩类。这类问题工厂模式是无能为力的。这时就需要把目光从“如何创建”转向“如何组合”。这正是可组合部件Composable Components的设计思路。3.2 什么是可组合部件可组合部件指的是系统由一组粒度适中、职责单一、接口清晰的组件构成这些组件可以通过不同的排列组合方式搭建成新的功能就像乐高积木一样。组合方式和设计模式的关系非常密切策略模式把可变的算法封装成独立策略运行时动态替换。装饰器模式在不修改原有类的情况下为对象动态添加职责。责任链模式把多个处理者串成一条链请求沿着链传递。管道-过滤器模式把数据处理流程拆成多个过滤器数据依次流过每个过滤器。这些模式的共同点是它们都强调把大功能拆分成小部件再通过组合的方式装配起来。3.3 从“继承”到“组合”的关键转变面向对象设计中有一句经典原则组合优于继承Composition over Inheritance。继承的问题在于父类的变更会波及所有子类子类容易继承到不需要的行为多层继承会让系统越来越难以维护。而组合的方式是一个对象持有另一个对象的引用通过委托调用其方法。这种方式更加灵活因为组合关系可以在运行时动态改变而继承关系在编译期就固定了。举个例子一个TextProcessor需要做“去 HTML 标签 → 转小写 → 去除停用词”三步处理。如果使用继承你可能会定义HtmlRemoverTextProcessor、LowerCaseTextProcessor等子类但组合方式更推荐这样做。4. 软件工厂里的“流水线”用组合模式搭建处理管线4.1 场景描述假设我们要设计一个文本处理模块它需要接收用户输入的原始文本经过一系列处理步骤后输出干净的文本。处理步骤可能包括去除 HTML 标签去除多余的空白字符转换为小写过滤敏感词这些步骤可能会根据业务需求动态调整比如有的场景需要保留大小写有的场景不需要过滤敏感词。如果用继承做死代码会非常僵硬。4.2 定义处理器接口首先定义一个统一的处理器接口// 文件路径com.example.pipeline.TextProcessor.java public interface TextProcessor { String process(String input); }每一个具体步骤都实现这个接口// 文件路径com.example.pipeline.HtmlTagRemover.java public class HtmlTagRemover implements TextProcessor { Override public String process(String input) { return input.replaceAll([^]*, ); } }// 文件路径com.example.pipeline.WhitespaceNormalizer.java public class WhitespaceNormalizer implements TextProcessor { Override public String process(String input) { return input.trim().replaceAll(\\s, ); } }// 文件路径com.example.pipeline.LowerCaseConverter.java public class LowerCaseConverter implements TextProcessor { Override public String process(String input) { return input.toLowerCase(); } }// 文件路径com.example.pipeline.SensitiveWordFilter.java public class SensitiveWordFilter implements TextProcessor { private final ListString sensitiveWords; public SensitiveWordFilter(ListString sensitiveWords) { this.sensitiveWords sensitiveWords; } Override public String process(String input) { String result input; for (String word : sensitiveWords) { result result.replaceAll(word, ***); } return result; } }4.3 定义组合处理器接下来定义一个组合处理器它内部维护一个处理步骤列表并按顺序调用// 文件路径com.example.pipeline.CompositeTextProcessor.java import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class CompositeTextProcessor implements TextProcessor { private final ListTextProcessor processors new ArrayList(); public CompositeTextProcessor(TextProcessor... processors) { this.processors.addAll(Arrays.asList(processors)); } public void addProcessor(TextProcessor processor) { this.processors.add(processor); } public void addProcessors(ListTextProcessor processors) { this.processors.addAll(processors); } Override public String process(String input) { String result input; for (TextProcessor processor : processors) { result processor.process(result); } return result; } }这里的关键点在于CompositeTextProcessor本身也实现了TextProcessor接口所以它既可以作为一个独立处理器被调用也可以被嵌入到更大的组合中。这就是“可组合部件”的核心特征每个部件既能独立工作也能被组合到更大的结构中。4.4 写一个测试程序验证效果// 文件路径com.example.PipelineDemo.java import java.util.List; public class PipelineDemo { public static void main(String[] args) { // 1. 创建各个处理步骤 TextProcessor htmlRemover new HtmlTagRemover(); TextProcessor whitespaceNormalizer new WhitespaceNormalizer(); TextProcessor lowerCaseConverter new LowerCaseConverter(); TextProcessor sensitiveWordFilter new SensitiveWordFilter( List.of(敏感词, 广告) ); // 2. 按业务需求组装管线 CompositeTextProcessor pipeline new CompositeTextProcessor( htmlRemover, whitespaceNormalizer, lowerCaseConverter, sensitiveWordFilter ); // 3. 输入原始文本 String input pHello World, 这里是strong敏感词/strong测试/p ; String output pipeline.process(input); System.out.println(原始文本: input); System.out.println(处理结果: output); } }预期输出类似原始文本: pHello World, 这里是strong敏感词/strong测试/p 处理结果: hello world, 这里是***测试如果某一天业务要求“不需要转小写”只需要从组合中移除LowerCaseConverter即可完全不需要改动任何其他类。这正是可组合部件带来的直接收益灵活、可插拔、可复用。5. 多 Agent 场景中的“可组合部件”如何理解 subagent 即工具5.1 从传统软件到 AI Agent 架构近两年随着大语言模型LLM的发展软件系统的构建出现了一个新趋势传统的“代码即逻辑”开始向“智能体Agent编排”演进。设计模式在这个领域并没有过时反而以新的形态被继承了下来。在最新的多 Agent 设计中“主从模式”是一种很常见的架构。主 Agent 负责理解用户意图、拆解任务、调度资源子 Agentsubagent负责执行具体子任务。这个模式和传统软件中的“主管-工人”模式、甚至和工厂模式在思想上一脉相承。但这里有一个非常值得注意的观点在多 Agent 设计中主从模式的本质其实是将 subagent 视作另一种形式的 tool工具进行调用。5.2 为什么说 subagent 是“另类的工具”传统工具tool通常是一个函数、一个 API 接口或一个独立服务。而 subagent 也可以被看作是具有“输入-输出”边界的黑盒组件主 Agent 传入一个任务描述subagent 返回一个结果。这个过程和调用一个函数并没有本质区别。这种设计思路天然契合“可组合部件”的理念每个 subagent 职责单一只完成特定类型的任务。每个 subagent 有清晰的输入输出接口。主 Agent 可以通过组合不同的 subagent 来应对不同需求。举例来说一个智能客服系统可以拆分成以下 Agent意图识别 Agent判断用户问题属于哪个类别。订单查询 Agent查询订单状态。退款处理 Agent处理退款流程。话术生成 Agent生成回复话术。主 Agent 根据意图判断结果决定调用哪个子 Agent。如果要增加“发票申请”能力只需要新增一个发票申请 Agent并在主 Agent 的调度逻辑中注册即可不需要改动其他 Agent。5.3 Agent 组件的设计原则把 subagent 视作可组合部件后设计原则就非常清晰了接口标准化所有 subagent 都应该有统一的输入输出格式比如都接收一个结构化的 JSON 字符串都返回一个结构化的结果。状态隔离subagent 之间不应共享可变状态所有通信通过主 Agent 转发。异常处理主 Agent 需要统一处理 subagent 的超时、失败、异常返回等场景。可观测性每个 subagent 的调用都应该有日志记录便于追踪和排查。这其实就是把传统软件工程中的“接口设计原则”平移到了 Agent 设计领域。所以说软件工厂设计模式并非只适用于传统代码开发在 AI 原生应用的构建中同样具有指导意义。6. 软件工厂中的“生产流程”从单一模式到模式组合6.1 设计模式不是孤立的在实际的软件工厂中设计模式很少单独使用。通常是一个系统同时使用多个模式各司其职。以订单系统为例使用工厂模式创建不同渠道的订单处理器淘宝、京东、拼多多。使用策略模式定义不同的优惠计算规则。使用责任链模式实现订单的校验流程库存校验、风控校验、地址校验。使用观察者模式在订单状态变化时通知库存服务、物流服务和用户通知服务。这些模式相互配合构成了一个松耦合、可扩展的系统。6.2 组件工厂把“创建”和“组装”统一起来如果系统中有大量可组合部件怎么管理它们的创建和装配答案是组合工厂模式。我们可以建立一个ProcessorFactory它负责创建各种处理器实例同时也负责组合出完整的处理管线// 文件路径com.example.factory.TextProcessorFactory.java import java.util.List; public class TextProcessorFactory { public TextProcessor createHtmlRemover() { return new HtmlTagRemover(); } public TextProcessor createWhitespaceNormalizer() { return new WhitespaceNormalizer(); } public TextProcessor createLowerCaseConverter() { return new LowerCaseConverter(); } public TextProcessor createSensitiveWordFilter(ListString words) { return new SensitiveWordFilter(words); } /** * 创建默认的完整处理管线 */ public TextProcessor createDefaultPipeline(ListString sensitiveWords) { return new CompositeTextProcessor( createHtmlRemover(), createWhitespaceNormalizer(), createLowerCaseConverter(), createSensitiveWordFilter(sensitiveWords) ); } /** * 创建不含敏感词过滤的管线 */ public TextProcessor createPipelineWithoutSensitiveFilter() { return new CompositeTextProcessor( createHtmlRemover(), createWhitespaceNormalizer(), createLowerCaseConverter() ); } }这样的设计有很直观的好处客户端代码不再需要直接 new 各种处理器。管线的组装逻辑集中在一个工厂类中便于统一管理。如果以后要调整组件组合方式只需要修改工厂类客户端代码不需要改动。6.3 运行示例// 文件路径com.example.FactoryPipelineDemo.java import java.util.List; public class FactoryPipelineDemo { public static void main(String[] args) { TextProcessorFactory factory new TextProcessorFactory(); TextProcessor defaultPipeline factory.createDefaultPipeline( List.of(敏感词, 广告) ); String rawText div这是一个em广告/em测试包含 Sensitive 内容/div; System.out.println(defaultPipeline.process(rawText)); TextProcessor simplePipeline factory.createPipelineWithoutSensitiveFilter(); System.out.println(simplePipeline.process(rawText)); } }输出结果这是一个***测试包含 sensitive 内容 这是一个广告测试包含 sensitive 内容这个示例可以体现出“软件工厂”的核心价值工厂负责标准化组件的生产同时负责按需提供不同配置的成品业务方只需要声明需求而不需要了解内部装配细节。7. 可组合部件设计的最佳实践与常见误区7.1 最佳实践在项目中落地软件工厂和可组合部件设计时以下几条经验值得参考接口设计要小且稳定。组件的接口是组合的基础。接口一旦定义好尽量不要频繁变动否则所有依赖方都要跟着改。每个组件只做一件事。如果组件职责过多组合时会变得难以复用。遵循单一职责原则。依赖注入优先于内部 new。组件内部如果有依赖尽量通过构造参数传入而不是自己 new。这样组件在不同上下文中可以被替换。组合关系在配置层描述。理想情况下哪些组件如何组合应该通过配置项或装配类来声明而不是散落在各处业务代码里。为组合提供默认实现。工厂类可以提供默认的组件和默认的组合方式这样最简单的使用场景不需要调用方写很多配置。注意组件的生命周期管理。有些组件有状态数据库连接、线程池有些组件是无状态的。设计时需要区分避免无状态组件被错误地共享可变状态。7.2 常见误区误区说明正确做法过度设计一上来就抽象十几个接口系统变得难以理解随着需求演进逐步抽象不要为不存在的变化做设计组件粒度太小每个方法都定义成独立组件接口爆炸以“可独立变更、可独立复用”为标准衡量粒度组合逻辑散落业务代码里直接 new 具体组件并拼接用工厂或装配类集中管理组合逻辑忽略组件的异常协议组件内部抛异常调用方无法统一处理定义统一的异常封装和错误码接口不稳定组件接口随着需求频繁修改接口变更要评估影响范围尽量向后兼容7.3 什么时候不应该使用这种设计说实话不是所有场景都适合软件工厂 可组合部件的设计模式。以下情况反而应该简化项目非常小只有两三个类抽象反而增加理解成本。业务逻辑基本不会变化不需要为“可能的变化”预留扩展点。团队对设计模式不熟悉强行引入会导致代码风格不统一。设计模式的正确使用方式是“在需要的地方使用”而不是“为了使用而使用”。软件工厂设计模式的核心价值在于应对变化如果系统本身不需要频繁变化那简单的实现往往比复杂的抽象更可靠。8. 面向未来的软件工厂从代码组件到智能体组件观察当前技术的发展软件工厂的“部件”形态正在发生变化。过去软件工厂的部件主要是类、函数、模块、服务。设计模式解决的是这些代码级部件的创建和组合问题。而现在随着 LLM 应用的普及软件的部件形态正在向“智能体”扩展。一个 Agent、一个 Tool、一个 Prompt 模板、一个 RAG 检索器都可以被看作是软件系统的可组合部件。这意味着Agent 编排系统就是新一代的“软件工厂装配线”。主从模式就是工厂流水线上的“工位调度逻辑”。将 subagent 视作 tool 调用就是工厂内部的标准件交换协议。这些思想并不是凭空产生的它们的根基依然是软件工程几十年来积累的设计智慧和设计模式。对开发者来说现在正是打好基础的好时机。掌握经典设计模式 可组合部件设计思想在未来无论是写传统后端服务、微服务架构还是转向 AI Agent 应用开发都能用到这些底层方法论。9. 总结与实践建议本文围绕“软件工厂设计模式”展开核心内容可以梳理为四条线索工厂模式是软件工厂的起点它解决了对象创建与业务逻辑解耦的问题是系统扩展性的基础。可组合部件是软件工厂的升级形态通过组合优于继承的原则将系统拆分为职责单一的组件再通过组合方式装配出复杂功能。组合管理的重心在装配层工厂类不仅是“创建者”还应该是“装配者”统一管理组件的创建和组合方式。可组合思想可以延伸到 Agent 设计多 Agent 系统中的主从模式和 subagent 即工具的设计思路与传统可组合部件设计一脉相承。如果你准备在实际项目中应用这套方法论建议按以下节奏推进第一步先识别系统中频繁变化的“点”比如接口接入方的类型、处理流程的步骤、算法实现的版本。第二步为变化点定义稳定的接口把可变部分封装为独立组件。第三步用工厂类统一创建和装配组件业务代码只依赖接口。第四步在项目演进中逐步迭代组件库形成团队内部可复用的“软件部件仓库”。软件工厂并不是一个具体的框架也不是某一个设计模式而是一种工程化思维。它提醒我们优秀的软件系统不是靠“一次成型”写出来的而是通过标准化的部件和灵活的装配方式持续演进出来的。掌握这种思维比记忆某个模式的代码模板更有长期价值。
返回列表