软件工厂设计模式:从工厂模式到可组合部件与Agent编排
2026/9/1 15:34:55 网站建设 项目流程

1. 从“软件工厂”说起:为什么我们需要可组合的构建方式

先聊一个很多团队都会遇到的问题:当业务规模逐渐扩大,代码仓库从几个类膨胀到上百个模块,新功能上线越来越慢,重复代码越来越多,不同开发者的实现风格也千差万别。此时你往往会听到一种声音——“我们的软件生产应该像工厂流水线一样标准化”。

这正是“软件工厂”这个概念的出发点。它并不是说要建一个物理上的工厂,而是一种软件构建的方法论:把软件系统拆分成标准化的部件,通过定义好的接口和流程进行装配,最终像流水线一样稳定地产出可交付的功能。

把这个思路落到技术层面,就绕不开两个关键词:设计模式和可组合部件。

  • 设计模式提供了经过验证的、可复用的解决方案模板,它告诉我们在特定场景下应该怎么组织类和对象。
  • 可组合部件则强调系统由多个独立、可替换、可复用的模块拼装而成,而不是一棵难以拆分的“大泥球”。

两者结合,就是我们今天要聊的主题:软件工厂设计模式。它不特指某一个设计模式,而是一套用“工厂思维 + 组合思维”来构建软件系统的方法论。本文会从经典设计模式中的工厂模式讲起,逐步过渡到可组合部件设计,最后给出一个完整的代码示例,帮助你把这套思路落地到实际项目中。

2. 设计模式里的“工厂”:从 Factory Method 到 Abstract Factory

2.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 标签 → 转小写 → 去除停用词”三步处理。如果使用继承,你可能会定义HtmlRemoverTextProcessorLowerCaseTextProcessor等子类,但组合方式更推荐这样做。

4. 软件工厂里的“流水线”:用组合模式搭建处理管线

4.1 场景描述

假设我们要设计一个文本处理模块,它需要接收用户输入的原始文本,经过一系列处理步骤后输出干净的文本。处理步骤可能包括:

  1. 去除 HTML 标签
  2. 去除多余的空白字符
  3. 转换为小写
  4. 过滤敏感词

这些步骤可能会根据业务需求动态调整,比如有的场景需要保留大小写,有的场景不需要过滤敏感词。如果用继承做死,代码会非常僵硬。

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 List<String> sensitiveWords; public SensitiveWordFilter(List<String> 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 List<TextProcessor> 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(List<TextProcessor> 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 = " <p>Hello World, 这里是<strong>敏感词</strong>测试</p> "; String output = pipeline.process(input); System.out.println("原始文本: " + input); System.out.println("处理结果: " + output); } }

预期输出类似:

原始文本: <p>Hello World, 这里是<strong>敏感词</strong>测试</p> 处理结果: hello world, 这里是***测试

如果某一天业务要求“不需要转小写”,只需要从组合中移除LowerCaseConverter即可,完全不需要改动任何其他类。这正是可组合部件带来的直接收益:灵活、可插拔、可复用。

5. 多 Agent 场景中的“可组合部件”:如何理解 subagent 即工具

5.1 从传统软件到 AI Agent 架构

近两年,随着大语言模型(LLM)的发展,软件系统的构建出现了一个新趋势:传统的“代码即逻辑”开始向“智能体(Agent)编排”演进。设计模式在这个领域并没有过时,反而以新的形态被继承了下来。

在最新的多 Agent 设计中,“主从模式”是一种很常见的架构。主 Agent 负责理解用户意图、拆解任务、调度资源;子 Agent(subagent)负责执行具体子任务。这个模式和传统软件中的“主管-工人”模式、甚至和工厂模式在思想上一脉相承。

但这里有一个非常值得注意的观点:在多 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(List<String> words) { return new SensitiveWordFilter(words); } /** * 创建默认的完整处理管线 */ public TextProcessor createDefaultPipeline(List<String> sensitiveWords) { return new CompositeTextProcessor( createHtmlRemover(), createWhitespaceNormalizer(), createLowerCaseConverter(), createSensitiveWordFilter(sensitiveWords) ); } /** * 创建不含敏感词过滤的管线 */ public TextProcessor createPipelineWithoutSensitiveFilter() { return new CompositeTextProcessor( createHtmlRemover(), createWhitespaceNormalizer(), createLowerCaseConverter() ); } }

这样的设计有很直观的好处:

  1. 客户端代码不再需要直接 new 各种处理器。
  2. 管线的组装逻辑集中在一个工厂类中,便于统一管理。
  3. 如果以后要调整组件组合方式,只需要修改工厂类,客户端代码不需要改动。

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. 总结与实践建议

本文围绕“软件工厂设计模式”展开,核心内容可以梳理为四条线索:

  1. 工厂模式是软件工厂的起点:它解决了对象创建与业务逻辑解耦的问题,是系统扩展性的基础。
  2. 可组合部件是软件工厂的升级形态:通过组合优于继承的原则,将系统拆分为职责单一的组件,再通过组合方式装配出复杂功能。
  3. 组合管理的重心在装配层:工厂类不仅是“创建者”,还应该是“装配者”,统一管理组件的创建和组合方式。
  4. 可组合思想可以延伸到 Agent 设计:多 Agent 系统中的主从模式和 subagent 即工具的设计思路,与传统可组合部件设计一脉相承。

如果你准备在实际项目中应用这套方法论,建议按以下节奏推进:

  • 第一步,先识别系统中频繁变化的“点”,比如接口接入方的类型、处理流程的步骤、算法实现的版本。
  • 第二步,为变化点定义稳定的接口,把可变部分封装为独立组件。
  • 第三步,用工厂类统一创建和装配组件,业务代码只依赖接口。
  • 第四步,在项目演进中逐步迭代组件库,形成团队内部可复用的“软件部件仓库”。

软件工厂并不是一个具体的框架,也不是某一个设计模式,而是一种工程化思维。它提醒我们:优秀的软件系统不是靠“一次成型”写出来的,而是通过标准化的部件和灵活的装配方式,持续演进出来的。掌握这种思维,比记忆某个模式的代码模板更有长期价值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询