设计模式之简单工厂模式:把对象创建从业务代码中拿出去
欢迎你来读这篇博客,这篇博客主要是关于简单工厂模式。
其中包括了简单工厂模式的基本概念、适用场景、优缺点,以及一个贴近 Java 后端业务开发的完整案例。
序言
写业务代码时,我们经常会遇到这样的场景:
根据不同的类型,创建不同的对象,然后调用它们统一的业务方法。
比如:
- 根据支付渠道创建不同的支付处理器;
- 根据消息类型创建不同的消息发送器;
- 根据文件格式创建不同的文件解析器;
- 根据订单类型创建不同的结算策略;
- 根据导出类型创建不同的报表导出器。
如果不做任何设计,最常见的写法就是在业务代码里写一堆 if-else 或 switch。
这种写法刚开始看起来很直观,但随着业务越来越多,代码会慢慢变成这样:
1 | |
这时候问题就来了:
- 创建对象的逻辑散落在各个业务类里;
- 每增加一种类型,都要修改原来的业务代码;
- 业务代码既要关心“做什么”,又要关心“创建谁”;
- 重复判断越来越多,维护成本越来越高。
简单工厂模式要解决的,就是这个非常朴素但很常见的问题:
把对象创建逻辑从业务代码中抽离出来,交给一个专门的工厂类统一负责。
说白了,业务代码别天天在那儿“手搓对象”了,创建对象这种杂活交给工厂。业务代码负责业务,工厂负责生产。分工清楚,大家都轻松。
正文
chapter 1 简单工厂模式是什么
简单工厂模式,又叫静态工厂模式,它并不是 GoF 23 种经典设计模式之一,但在实际开发中非常常见。
它的核心思想是:
定义一个工厂类,由这个工厂类根据传入的参数决定创建哪一种具体产品对象。
简单工厂模式一般包含四类角色:
| 角色 | 说明 |
|---|---|
| 抽象产品 | 定义产品的统一接口或抽象类 |
| 具体产品 | 实现抽象产品的具体类 |
| 简单工厂 | 根据参数创建具体产品对象 |
| 客户端 | 调用工厂获取对象,并使用抽象产品接口完成业务 |
它的结构并不复杂:
classDiagram
class Client {
+execute()
}
class SimpleFactory {
+create(type) Product
}
class Product {
<<interface>>
+operation()
}
class ConcreteProductA {
+operation()
}
class ConcreteProductB {
+operation()
}
class ConcreteProductC {
+operation()
}
Client --> SimpleFactory
SimpleFactory --> Product
Product <|.. ConcreteProductA
Product <|.. ConcreteProductB
Product <|.. ConcreteProductC
从调用关系看,客户端不再直接依赖具体实现类,而是依赖抽象产品接口。
也就是说,客户端只知道:
我要一个能完成某件事的对象。
但它不需要知道:
这个对象到底是哪个具体类 new 出来的。
这就是简单工厂模式的价值。
chapter 2 为什么需要简单工厂模式
假设我们有一个文件导入功能,用户可以上传不同格式的订单文件,系统需要根据文件类型进行解析。
目前支持三种文件格式:
- CSV;
- Excel;
- JSON。
如果直接在业务类里判断,代码可能是这样的:
1 | |
这段代码能跑,但设计上不太舒服。
主要问题有三个。
第一,OrderImportService 既负责订单导入业务,又负责创建具体解析器,职责不够单一。
第二,具体类名直接暴露在业务服务中,比如 CsvOrderFileParser、ExcelOrderFileParser、JsonOrderFileParser,导致业务服务和具体实现类耦合。
第三,如果后续新增 XML 文件解析,就要修改 OrderImportService 的判断逻辑。一个小需求,动到核心业务类,多少有点“为了加个插座,把墙拆了”。
因此,我们可以把解析器的创建过程抽出来,交给一个简单工厂。
chapter 3 案例背景:订单文件导入解析
接下来用一个 Java 后端常见业务做案例:
订单导入服务根据文件类型,选择不同的文件解析器,将上传文件解析为统一的订单导入命令对象。
业务目标如下:
- CSV 文件使用
CsvOrderFileParser解析; - Excel 文件使用
ExcelOrderFileParser解析; - JSON 文件使用
JsonOrderFileParser解析; - 业务服务只依赖统一的
OrderFileParser接口; - 对象创建逻辑统一放到
OrderFileParserFactory中。
改造后的结构如下:
classDiagram
class OrderImportService {
-OrderFileParserFactory parserFactory
+importOrder(fileType, fileContent)
}
class OrderFileParserFactory {
+createParser(fileType) OrderFileParser
}
class OrderFileParser {
<<interface>>
+parse(fileContent) List~OrderImportCommand~
}
class CsvOrderFileParser {
+parse(fileContent) List~OrderImportCommand~
}
class ExcelOrderFileParser {
+parse(fileContent) List~OrderImportCommand~
}
class JsonOrderFileParser {
+parse(fileContent) List~OrderImportCommand~
}
OrderImportService --> OrderFileParserFactory
OrderFileParserFactory --> OrderFileParser
OrderFileParser <|.. CsvOrderFileParser
OrderFileParser <|.. ExcelOrderFileParser
OrderFileParser <|.. JsonOrderFileParser
chapter 4 定义文件类型枚举
先定义一个文件类型枚举,避免在代码中到处散落魔法字符串。
1 | |
这里没有直接在业务代码中使用字符串判断,而是统一转换为枚举。
这样做有几个好处:
- 文件类型有明确范围;
- 参数校验集中处理;
- 后续新增类型时更容易定位;
- 业务代码的可读性更好。
chapter 5 定义抽象产品接口
接着定义订单文件解析器接口。
1 | |
这个接口就是简单工厂模式中的“抽象产品”。
它规定所有文件解析器都必须提供 parse 方法。
至于 CSV 怎么解析、Excel 怎么解析、JSON 怎么解析,业务服务不关心,具体实现类自己负责。
chapter 6 定义统一的订单导入对象
解析后的数据可以统一转换成一个命令对象。
1 | |
实际项目里,这个对象可以放在 application.command、dto 或者 request 包中,具体看你的分层规范。
如果你采用 DDD 风格,它更像一个应用层命令对象。
chapter 7 定义具体产品类
现在分别实现三种解析器。
CSV 解析器
1 | |
Excel 解析器
1 | |
JSON 解析器
1 | |
这三个具体类就是简单工厂模式中的“具体产品”。
它们都实现了同一个接口,所以业务代码可以统一面向 OrderFileParser 编程。
chapter 8 编写简单工厂类
现在把对象创建逻辑集中到工厂类中。
1 | |
这就是一个典型的简单工厂。
它的职责非常明确:
根据文件类型,创建对应的订单文件解析器。
业务代码不再需要知道 CsvOrderFileParser、ExcelOrderFileParser、JsonOrderFileParser 这些具体实现类。
业务代码只需要向工厂要一个 OrderFileParser。
chapter 9 改造业务服务
业务服务改造后如下:
1 | |
现在 OrderImportService 的职责就清晰了:
- 它不负责创建具体解析器;
- 它只负责订单导入的业务流程;
- 它只依赖
OrderFileParser抽象能力; - 具体解析器由工厂负责选择。
这就是简单工厂模式带来的最直接收益:
创建逻辑集中,业务逻辑变干净。
chapter 10 简单测试一下
可以写一个简单的 main 方法测试:
1 | |
运行后会输出:
1 | |
这个案例虽然简单,但它已经展示了简单工厂模式的核心价值。
chapter 11 如果放到 Spring Boot 项目中怎么写
在 Spring Boot 项目里,具体解析器通常不需要手动 new,而是交给 Spring 容器管理。
一种更常见的写法是:
1 | |
每个解析器声明自己支持的类型:
1 | |
然后工厂通过构造器注入所有解析器:
1 | |
这种写法比 switch 更适合真实项目。
它的好处是:
- 具体对象由 Spring 管理;
- 解析器可以继续注入其他依赖,比如 Repository、Redis、配置类;
- 新增解析器时,只需要新增一个实现类并加上
@Component; - 工厂类本身不需要频繁修改。
不过严格来说,这已经不再是最原始的“静态简单工厂”,而是结合 Spring 容器后的工厂注册表写法。
这也是实际工程里更推荐的方式。
chapter 12 简单工厂模式的优点
简单工厂模式的优点主要有四个。
1. 封装对象创建逻辑
客户端不用到处写 new,也不用关心具体实现类。
对象创建被集中到了工厂类里,业务代码更干净。
2. 降低客户端和具体类的耦合
客户端只依赖抽象产品接口。
比如 OrderImportService 依赖的是 OrderFileParser,而不是 CsvOrderFileParser。
这样后续替换具体实现时,对业务代码影响更小。
3. 代码结构更清晰
简单工厂把“创建对象”和“使用对象”分开。
这符合单一职责原则,也更利于后续维护。
4. 适合小规模对象创建场景
如果类型数量不多,变化也不频繁,简单工厂非常好用。
它没有太多抽象层,理解成本低,上手快。
chapter 13 简单工厂模式的缺点
简单工厂模式也不是银弹。
它最大的缺点是:
新增产品类型时,往往需要修改工厂类。
比如新增 XML 解析器:
1 | |
这会违反开闭原则:
对扩展开放,对修改关闭。
当然,违反开闭原则不代表它不能用。
设计模式不是洁癖比赛,工程代码也不是“抽象体操锦标赛”。
如果你的类型就三五个,而且一年都不变几次,简单工厂非常合适。
但如果你的产品类型经常扩展,或者对象创建逻辑非常复杂,就应该考虑工厂方法模式、抽象工厂模式,或者 Spring 的策略注册表模式。
chapter 14 简单工厂模式适用场景
简单工厂模式适合下面这些场景。
1. 需要根据参数创建不同对象
例如:
- 根据
fileType创建不同文件解析器; - 根据
payType创建不同支付处理器; - 根据
messageType创建不同消息发送器; - 根据
exportType创建不同报表导出器。
2. 产品类型数量较少
如果具体产品只有几个,而且短期不会频繁变化,简单工厂足够用。
别一上来就抽象工厂、工厂方法、策略模式、SPI 全套组合拳。
小需求用小设计,复杂业务再上复杂结构。
3. 客户端不应该关心具体类
当业务代码只想使用某种能力,而不想关心具体实现类时,可以使用简单工厂。
例如订单导入服务只关心“解析文件”,不应该关心到底是 CSV 解析器还是 Excel 解析器。
4. 创建逻辑需要统一管理
有些对象创建前需要统一参数校验、默认值处理、日志记录、监控埋点。
这类逻辑如果散落在各个业务类里,很容易重复。
简单工厂可以把这些创建规则集中起来。
chapter 15 不适合使用简单工厂的场景
下面这些场景不太适合使用简单工厂。
1. 产品类型频繁增加
如果系统经常新增产品类型,每次都要修改工厂类,维护成本会变高。
这种情况下可以考虑工厂方法模式,或者在 Spring 中使用 Map<类型, 实现类> 的注册表模式。
2. 产品族关系复杂
如果不是创建一个对象,而是要创建一组相关对象,比如:
- MySQL 数据库连接器 + MySQL SQL 生成器 + MySQL 方言处理器;
- PostgreSQL 数据库连接器 + PostgreSQL SQL 生成器 + PostgreSQL 方言处理器。
这种情况更适合抽象工厂模式。
3. 工厂类越来越臃肿
如果一个工厂里塞了几十种创建逻辑,这个工厂本身就会变成维护灾难。
工厂是来灭火的,不是来变成新火源的。
4. 创建逻辑包含复杂业务规则
如果对象创建本身依赖复杂业务判断,比如用户等级、地区、活动状态、灰度配置、A/B 实验等,那么简单工厂可能不够。
这时候可以考虑策略模式、规则引擎,或者责任链模式。
chapter 16 简单工厂和工厂方法的区别
简单工厂模式和工厂方法模式很容易混淆。
可以简单这样理解:
| 对比点 | 简单工厂模式 | 工厂方法模式 |
|---|---|---|
| 是否属于 GoF 23 种模式 | 不是 | 是 |
| 谁负责创建对象 | 一个统一工厂类 | 多个具体工厂类 |
| 新增产品时是否修改原工厂 | 通常需要 | 通常不需要 |
| 复杂度 | 低 | 中等 |
| 适合场景 | 类型少、变化少 | 类型多、扩展频繁 |
简单工厂像一个总服务台:
你告诉它要什么,它直接给你。
工厂方法像多个专业窗口:
每个窗口负责生产自己那一类对象。
所以,简单工厂更适合早期业务和轻量场景。
当产品类型越来越多、变化越来越频繁时,再升级成工厂方法模式也不迟。
chapter 17 实践建议
写简单工厂模式时,建议注意下面几点。
1. 不要到处使用魔法字符串
不要这样写:
1 | |
更推荐使用枚举:
1 | |
如果入参来自前端或外部系统,再统一转换成枚举。
2. 工厂只负责创建,不要塞业务流程
工厂类最好只做对象选择和创建。
不要把订单校验、库存扣减、数据库保存、消息发送都塞进工厂。
否则工厂很快就会变成“业务大杂烩”。
3. 产品接口要稳定
简单工厂依赖统一抽象接口。
如果每个具体产品的方法都不一样,就说明抽象没有提好。
例如下面这种接口就不太好:
1 | |
这不是抽象,这是把具体实现硬塞进接口。
更好的方式是:
1 | |
4. 在 Spring 项目中优先考虑容器管理
如果具体产品类需要注入其他组件,不建议在工厂里直接 new。
比如解析器里要注入:
- Repository;
- RedisTemplate;
- ObjectMapper;
- 配置类;
- 其他领域服务。
这时应该让 Spring 管理具体实现类,然后工厂只负责从容器已有 Bean 中选择合适对象。
5. 不要过度设计
简单工厂本身就适合简单场景。
如果只有两个实现类,也没有复杂创建逻辑,直接写 switch 也不是罪过。
设计模式的目标不是炫技,而是降低复杂度。
代码写得像《易经》一样玄妙,后面维护的人大概率只想给你点一炷香。
chapter 18 小结
简单工厂模式的核心是:
用一个工厂类统一封装对象创建逻辑,让业务代码面向抽象编程,而不是直接依赖具体实现类。
它最适合解决这类问题:
根据不同类型创建不同对象。
它的主要优点是:
- 创建逻辑集中;
- 业务代码更干净;
- 客户端和具体实现类解耦;
- 理解成本低,适合入门和小型业务场景。
它的主要缺点是:
- 新增类型时可能要修改工厂类;
- 产品类型过多时工厂容易膨胀;
- 不适合复杂产品族创建。
所以,简单工厂模式可以作为设计模式学习的第一站。
它不复杂,但非常实用。
真正理解它之后,再学习工厂方法模式、抽象工厂模式、策略模式,会顺很多。
参考资料
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
- Eric Freeman, Elisabeth Robson. Head First Design Patterns.
- Joshua Bloch. Effective Java.
- Robert C. Martin. Clean Code: A Handbook of Agile Software Craftsmanship.
- Martin Fowler. Patterns of Enterprise Application Architecture.
启示录
简单工厂模式看起来简单,但它背后的思想非常重要:
业务代码应该关注业务本身,对象创建应该被集中管理。
很多复杂系统的问题,都是从一些不起眼的 new、if-else、switch 开始的。
一开始只是为了赶需求,后来就变成了没人敢动的祖传代码。
所以,不要小看简单工厂模式。
它不是最优雅的模式,却是最容易落地的模式之一。
当你发现业务代码里到处都在根据类型创建对象时,就可以想一想:
这里是不是该有一个工厂?
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。