本文是「设计模式实战解读」系列第二篇。系列文章统一按照 定义 → 痛点场景 → 模式结构 → 核心实现 → 真实应用 → 常见变种 → 优缺点 → 避坑指南 → FAQ 的结构展开,每篇聚焦一个模式讲透。
一句话定义
工厂模式(Factory):把对象的创建逻辑从使用者中剥离出来,由专门的工厂负责创建。调用方只关心"我要什么",不关心"怎么造"。
归属:创建型模式。
一、没有工厂时的痛点
假设你在做一个消息通知模块,需要支持多种发送渠道:
java
public void sendNotification(String channel, String message) {
if ("email".equals(channel)) {
EmailSender sender = new EmailSender("smtp.xxx.com", 465, "user", "pwd");
sender.send(message);
} else if ("sms".equals(channel)) {
SmsSender sender = new SmsSender("https://sms-api.xxx.com", "appKey", "secret");
sender.send(message);
} else if ("dingtalk".equals(channel)) {
DingTalkSender sender = new DingTalkSender("webhook_url", "sign_secret");
sender.send(message);
} else if ("wechat".equals(channel)) {
WeChatSender sender = new WeChatSender("corpId", "agentId", "secret");
sender.send(message);
}
}
问题:
- 违反开闭原则:每新增一种渠道,都要改这段代码
- 创建逻辑散落各处:多处需要创建 Sender,重复的构造参数到处写
- 调用方强耦合具体类:具体类改了,所有调用方都要改
- 无法做单元测试:直接 new 出来的对象没法被 mock
二、模式结构
工厂模式有三个层次:
层次 1: 简单工厂
Client → Factory.create(type) → ProductA / ProductB / ProductC
层次 2: 工厂方法
Client → AbstractFactory.create() → Product
↑ FactoryA → ProductA
↑ FactoryB → ProductB
层次 3: 抽象工厂
Client → AbstractFactory
├ createProductX() → X1 / X2
└ createProductY() → Y1 / Y2
三个层次的适用边界:
- 简单工厂:产品种类少(<10),新增频率低
- 工厂方法:产品种类多,经常新增,要做到加新品不改旧代码
- 抽象工厂:需要创建一组相关联的产品(如 UI 组件族:按钮+输入框+下拉框)
三、核心实现
3.1 简单工厂
java
// 产品接口
public interface MessageSender {
void send(String message, String target);
}
// 具体产品
public class EmailSender implements MessageSender { /* ... */ }
public class SmsSender implements MessageSender { /* ... */ }
public class DingTalkSender implements MessageSender { /* ... */ }
// 简单工厂
public class SenderFactory {
private static final Map<String, Supplier<MessageSender>> CREATORS = new HashMap<>();
static {
CREATORS.put("email", EmailSender::new);
CREATORS.put("sms", SmsSender::new);
CREATORS.put("dingtalk", DingTalkSender::new);
}
public static MessageSender create(String channel) {
Supplier<MessageSender> creator = CREATORS.get(channel);
if (creator == null) {
throw new IllegalArgumentException("Unsupported channel: " + channel);
}
return creator.get();
}
}
// 调用方(解耦了!)
MessageSender sender = SenderFactory.create("email");
sender.send("Hello", "test@example.com");
用 Map<String, Supplier> 代替 if-else,新增渠道只需往 Map 里注册,不改工厂逻辑。
3.2 工厂方法
当产品创建逻辑差异大(需要不同配置、不同依赖)时,每种产品对应一个工厂:
java
// 抽象工厂
public interface SenderFactory {
MessageSender create(SenderConfig config);
}
// 具体工厂
public class EmailSenderFactory implements SenderFactory {
@Override
public MessageSender create(SenderConfig config) {
// Email 特有的初始化:SMTP 连接、SSL 证书加载
SmtpClient smtp = new SmtpClient(config.getHost(), config.getPort());
smtp.auth(config.getUsername(), config.getPassword());
return new EmailSender(smtp);
}
}
public class DingTalkSenderFactory implements SenderFactory {
@Override
public MessageSender create(SenderConfig config) {
// 钉钉特有的初始化:签名计算、Token 缓存
DingTalkClient client = new DingTalkClient(config.getWebhook());
client.setSignSecret(config.getSecret());
return new DingTalkSender(client);
}
}
调用方通过注入不同工厂来切换产品,完全不耦合具体产品类。
3.3 结合 Spring 的工厂注册
在 Spring 项目中,工厂模式最常见的落地方式是策略 + 自动注册:
java
// 每种渠道的 Sender 标注自己的类型
@Component("email")
public class EmailSender implements MessageSender { /* ... */ }
@Component("sms")
public class SmsSender implements MessageSender { /* ... */ }
@Component("dingtalk")
public class DingTalkSender implements MessageSender { /* ... */ }
// 工厂:利用 Spring 自动收集所有实现
@Component
public class SenderFactory {
private final Map<String, MessageSender> senderMap;
// Spring 会自动把所有 MessageSender 按 Bean Name 注入到 Map
public SenderFactory(Map<String, MessageSender> senderMap) {
this.senderMap = senderMap;
}
public MessageSender getSender(String channel) {
MessageSender sender = senderMap.get(channel);
if (sender == null) {
throw new IllegalArgumentException("No sender for: " + channel);
}
return sender;
}
}
这是实际项目中用得最多的模式------零手动注册,新增一个 @Component 就自动生效。
四、真实应用场景
4.1 框架级应用
| 框架 | 工厂在哪 | 创建什么 |
|---|---|---|
| Spring | BeanFactory | Bean 实例 |
| MyBatis | SqlSessionFactory | SqlSession |
| SLF4J | LoggerFactory | Logger |
| JDBC | DriverManager | Connection |
| Jackson | ObjectMapper (builder) | Serializer/Deserializer |
| Netty | ChannelFactory | Channel |
4.2 业务场景
| 业务 | 工厂 | 产品 | 为什么用工厂 |
|---|---|---|---|
| 消息通知 | SenderFactory | 邮件/短信/钉钉/飞书 | 渠道多、各自初始化逻辑不同 |
| 支付 | PaymentFactory | 微信/支付宝/银联 | 支付方式差异大,策略隔离 |
| 文件解析 | ParserFactory | Excel/CSV/JSON/XML | 文件格式各自一套解析逻辑 |
| 连接器 | ConnectorFactory | HTTP/RPC/MQ/DB | 协议不同,连接方式差异大 |
| 报告导出 | ExporterFactory | PDF/Word/HTML | 渲染逻辑完全不同 |
| 规则引擎 | ConditionFactory | 等于/包含/大于/正则 | 条件类型多,各自求值逻辑独立 |
4.3 iPaaS 连接器场景
在 iPaaS 平台中,连接器(Connector)是工厂模式的经典落地:
用户配置的流程节点 → 指定了 connectorType = "dingtalk"
↓
ConnectorFactory.create("dingtalk")
↓
DingTalkConnector(已初始化好 Token 缓存、签名算法)
连接器的创建涉及 OAuth 初始化、Token 刷新、签名算法加载、SDK 实例化等重逻辑,不可能让业务代码直接 new。工厂把这些复杂性封装起来,调用方只需 factory.create(type) 拿到一个可用的连接器实例。
五、常见变种
5.1 静态工厂方法
不新建工厂类,在产品类上加静态方法:
java
public class Connection {
private Connection(String url) { /* ... */ }
// 静态工厂方法(命名比 new 更有语义)
public static Connection of(String url) { return new Connection(url); }
public static Connection pooled(String url, int poolSize) { /* ... */ }
public static Connection readonly(String url) { /* ... */ }
}
JDK 中大量使用:List.of()、Optional.of()、Integer.valueOf()、Collections.emptyList()。
优势:方法名有语义 + 可以返回缓存实例 + 可以返回子类型。
5.2 配置驱动工厂
产品类型由配置文件/数据库决定,工厂在运行时动态加载:
java
// 从配置中读取所有渠道 → 类名映射
// channel.email=com.xxx.EmailSender
// channel.sms=com.xxx.SmsSender
public class DynamicFactory {
private final Map<String, Class<?>> registry = new HashMap<>();
public void register(String type, String className) throws ClassNotFoundException {
registry.put(type, Class.forName(className));
}
public MessageSender create(String type) throws Exception {
return (MessageSender) registry.get(type).getDeclaredConstructor().newInstance();
}
}
这种方式配合 SPI(ServiceLoader)可以做到"不改代码、加 jar 包就能新增产品"。
5.3 延迟创建工厂
有些产品创建成本高,工厂不立即创建,而是返回一个"工厂引用":
java
public class LazyFactory {
public Supplier<HeavyObject> createLazy(Config config) {
// 不立即创建,返回一个 Supplier
return () -> new HeavyObject(config);
}
}
// 使用方在真正需要时才调用 .get()
Supplier<HeavyObject> lazy = factory.createLazy(config);
// ... 很多逻辑 ...
HeavyObject obj = lazy.get(); // 这时才真正创建
六、优缺点
| 优点 | 缺点 |
|---|---|
| 解耦创建逻辑和使用逻辑 | 增加类的数量(每种产品一个类/工厂) |
| 符合开闭原则(新增产品不改工厂) | 简单场景过度使用反而增加复杂度 |
| 集中管理创建逻辑,修改一处生效全局 | 产品需要遵循统一接口(有时会过度抽象) |
| 方便做缓存、池化、日志统计 | 调用方丧失了对象初始化的细粒度控制 |
| 对单元测试友好(可以注入 mock 工厂) | --- |
七、避坑指南
坑 1:简单工厂退化成巨型 if-else
用 Map<String, Supplier> 或 Spring 自动注入代替 if-else。如果仍然用 if-else,每次加渠道都改工厂类,开闭原则形同虚设。
坑 2:工厂方法过度使用
如果产品只有 2-3 种且不太会增加,直接用简单工厂就够了。为 3 种产品建 3 个工厂类 + 1 个抽象工厂,属于过度工程化。
坑 3:工厂和产品的依赖注入冲突
在 Spring 项目中,如果产品本身已经被 Spring 管理(@Component),那用 Map<String, T> 自动注入比手写工厂更简洁。别自己 new 产品再放进 Map------让 Spring 帮你干。
坑 4:工厂创建的对象未做生命周期管理
工厂创建了对象,但没人负责关闭/销毁------连接泄漏、线程泄漏。工厂如果创建有资源的对象(连接、流),应该同时提供 destroy() 或配合连接池使用。
坑 5:产品接口过度抽象
为了"统一",把完全不同的东西强行抽象成一个接口。比如 EmailSender 和 WebhookSender 的参数完全不同(邮件有收件人/抄送/附件,Webhook 只有 URL),非要统一成 send(String msg) 会导致各种 hack。
解法:允许产品接口有泛型参数,或者把差异部分放到 Config 对象里。
八、常见问题(FAQ)
Q:简单工厂、工厂方法、抽象工厂,什么时候用哪个?
A:大部分业务场景,简单工厂就够了。只有当满足以下条件时才升级:产品种类多(>10)且频繁新增 → 用工厂方法;需要创建一组相关联的产品族 → 用抽象工厂。90% 的实际项目只需要简单工厂。
Q:工厂模式和策略模式有什么区别?
A:工厂模式关注的是"创建"------如何构造一个对象。策略模式关注的是"行为"------如何选择一种算法来执行。很多时候二者结合使用:工厂创建出策略对象,调用方使用策略对象。区别在于侧重点不同。
Q:Spring 的 BeanFactory 是不是工厂模式?
A:是的,而且是工厂模式最经典的工业级实现。它不仅创建 Bean,还管理 Bean 的生命周期(初始化、销毁)、作用域(singleton/prototype/request)、依赖注入、AOP 代理。可以理解为"超级工厂"。
Q:什么时候不应该用工厂?
A:对象创建逻辑简单(直接 new 就行、没有依赖)、产品不需要面向接口编程、不需要 mock 测试------这些场景直接 new 比引入工厂更简洁。不要为了设计模式而设计模式。
Q:工厂创建的对象应该是单例还是每次 new?
A:取决于产品是否有状态。无状态的服务类(如 Sender)可以缓存复用;有状态的实例(如正在使用的 Connection)每次创建。通常在工厂内加一层缓存判断即可。
九、小结
工厂模式的核心价值只有一句话:把 "new" 的权利从调用方收回来,交给专门的角色管理。
记住三个实践要点:
- Spring 项目优先用
Map<String, T>自动注入,零代码实现工厂效果 - 简单工厂用 Map + Supplier 代替 if-else,满足开闭原则
- 工厂创建的对象别忘记生命周期管理------谁创建谁负责销毁
下一篇我们聊模板方法模式------当一系列操作有固定的"骨架"但细节各不相同时,如何做到复用骨架、开放细节。
标签:#设计模式 #工厂模式 #Factory #简单工厂 #工厂方法 #抽象工厂 #Java #Spring #创建型模式 #策略模式 #依赖注入 #面向对象 #软件工程