很多人聊"工厂模式"时会下意识地说"工厂模式嘛,就是 XxxFactory.create()"——可一旦有人追问"那简单工厂、静态工厂、工厂方法、抽象工厂到底有啥区别?",往往就答不上来了。
这四个词长得像,但实际上只有两个属于 GoF 23 种正统设计模式,剩下两个是工程实践里大家叫顺嘴的"非正式说法"。今天我们就把它们一次性捋清楚。
先从一个让人头大的例子说起
假设我们要做一个发送通知的功能,有三种渠道:短信、邮件、推送。最直觉的写法是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class OrderService {
public void notifyUser(String channel, String userId, String content) {
if ("sms".equals(channel)) {
SmsSender sender = new SmsSender("ak", "sk");
sender.send(userId, content);
} else if ("email".equals(channel)) {
EmailSender sender = new EmailSender("smtp.xx.com", 25);
sender.send(userId, content);
} else if ("push".equals(channel)) {
PushSender sender = new PushSender("appKey");
sender.send(userId, content);
}
}
}
|
这种写法的麻烦之处在于:
- 创建逻辑散落各处:哪天
SmsSender 的构造参数变了,所有 new SmsSender(...) 的地方都要改。 - 业务和"造对象"耦合:
OrderService 本不该知道 SmsSender 的 ak/sk 怎么填。 - 新增渠道要改老代码:再来一个微信渠道,又得加一个
else if,老代码越改越脏,违反开闭原则(OCP)。 - 不好测:业务方法里硬编码了
new,单元测试想替换成 mock 都难。
工厂模式要解决的就是这些痛点——把"用什么对象"和"对象怎么造"拆开。
一、简单工厂(Simple Factory)
它是什么
把"造对象"的逻辑收拢到一个工厂类的一个方法里,调用方只传"类型标识",不关心具体实现。
1
2
3
4
5
6
7
8
9
10
11
| public class NotificationSenderFactory {
public static Sender create(String channel) {
switch (channel) {
case "sms": return new SmsSender("ak", "sk");
case "email": return new EmailSender("smtp.xx.com", 25);
case "push": return new PushSender("appKey");
default: throw new IllegalArgumentException("unknown channel: " + channel);
}
}
}
|
业务代码立刻清爽:
1
2
| Sender sender = NotificationSenderFactory.create(channel);
sender.send(userId, content);
|
它的优缺点
优点:
- 创建逻辑集中到一个地方,构造参数变了只改一处。
- 业务代码只面向
Sender 接口编程,不依赖具体实现。
缺点:
- 新增类型要改
create 方法——违反开闭原则。 - 工厂方法里塞满
switch,类型多了之后会很臃肿。
一个常见的误解
很多书会把"简单工厂"列成一种设计模式,但 GoF 23 种里其实没有它。它更像是一种"惯用写法",是迈向真正工厂模式的过渡形态。
二、静态工厂方法(Static Factory Method)
“静态工厂"这个词在中文社区有点歧义,常见有两层含义:
- 狭义:上面简单工厂里那个方法本身是
static 的,所以也叫静态工厂——这层含义没什么新东西。 - 广义(更值得讲的一层):Effective Java 第 1 条提倡的——用类内部的静态工厂方法替代
public 构造器。
第二种含义长什么样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public final class Color {
private final int r, g, b;
private Color(int r, int g, int b) { // 构造器私有
this.r = r; this.g = g; this.b = b;
}
public static Color of(int r, int g, int b) {
return new Color(r, g, b);
}
public static Color fromHex(String hex) {
int rgb = Integer.parseInt(hex.substring(1), 16);
return new Color((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
}
public static Color black() {
return BLACK; // 可以缓存常用实例
}
private static final Color BLACK = new Color(0, 0, 0);
}
|
JDK 里到处都是这个套路:
Integer.valueOf(int):缓存了 -128~127 的实例List.of(...) / Map.of(...):返回不可变实现Optional.of(x) / Optional.empty()Files.newInputStream(path):返回 InputStream 接口,实现类对外不可见
为什么推荐它
相比 new 构造器,静态工厂方法有四个真正有价值的优点:
- 方法可以有名字——
Color.fromHex("#FF0000") 比 new Color(255, 0, 0) 表达力强得多。一个类想要"多种构造方式"时,构造器只能靠参数列表区分,名字不够用。 - 不必每次都创建新对象——可以缓存、可以复用。
Integer.valueOf 和单例都是这个原理。 - 可以返回子类型甚至接口类型——隐藏具体实现,调用方只面向接口编程。
Files.newInputStream 就是个例子。 - 返回类型可以根据参数动态决定——
EnumSet.of(...) 会根据元素数量返回 RegularEnumSet 或 JumboEnumSet,调用方不感知。
它的代价
- 类如果没有
public 构造器,就不能被子类继承——这其实有时是好事(强制组合优于继承)。 - 静态工厂方法和普通静态方法在 IDE 里不易区分,所以业界约定了一组命名习惯:
of / valueOf / from / getInstance / newInstance / create。看到这些名字,老司机就知道这是个工厂。
三、工厂方法(Factory Method)—— GoF 正统
简单工厂的最大问题是:新增类型要改 switch。工厂方法就是为了解决这个问题——把工厂本身也抽象化。
类关系
classDiagram
class SenderFactory {
<>
+create() Sender
}
class Sender {
<>
+send(userId, content)
}
class SmsSenderFactory
class EmailSenderFactory
class SmsSender
class EmailSender
SenderFactory <|.. SmsSenderFactory
SenderFactory <|.. EmailSenderFactory
Sender <|.. SmsSender
Sender <|.. EmailSender
SmsSenderFactory ..> SmsSender : creates
EmailSenderFactory ..> EmailSender : creates每种产品配一个工厂,工厂自己也面向接口编程。
代码长什么样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public interface Sender {
void send(String userId, String content);
}
public interface SenderFactory {
Sender create();
}
public class SmsSenderFactory implements SenderFactory {
@Override public Sender create() {
return new SmsSender("ak", "sk");
}
}
public class EmailSenderFactory implements SenderFactory {
@Override public Sender create() {
return new EmailSender("smtp.xx.com", 25);
}
}
|
调用方持有一个 SenderFactory,至于背后是哪种工厂,由配置或 IoC 容器注入:
1
2
3
4
5
6
7
8
9
10
11
12
| public class OrderService {
private final SenderFactory factory; // 依赖抽象
public OrderService(SenderFactory factory) {
this.factory = factory;
}
public void notifyUser(String userId, String content) {
Sender sender = factory.create();
sender.send(userId, content);
}
}
|
关键收益
新增一种"微信渠道"时:
- 加一个
WeChatSender 实现类 - 加一个
WeChatSenderFactory 实现类 - 老代码一行不动
这就是开闭原则的真正落地。代价是类的数量翻倍,所以只在"扩展性收益 > 类爆炸成本"时才值得用。
四、抽象工厂(Abstract Factory)—— GoF 正统
工厂方法解决的是"一种产品有多种实现”。当一个场景里同时有多种产品,且这些产品需要"配套使用"时,就需要抽象工厂。
经典场景:跨平台 UI
假设我们做一个跨平台 GUI 库,要支持 Windows 和 Mac,每个平台都有自己的 Button 和 Checkbox:
| 产品 \ 平台 | Windows | Mac |
|---|
| Button | WindowsButton | MacButton |
| Checkbox | WindowsCheckbox | MacCheckbox |
约束:Windows 程序里不能混入 Mac 风格的控件,反之亦然。
类关系如下:
classDiagram
class UIFactory {
<>
+createButton() Button
+createCheckbox() Checkbox
}
class Button {
<>
}
class Checkbox {
<>
}
class WindowsUIFactory
class MacUIFactory
class WindowsButton
class WindowsCheckbox
class MacButton
class MacCheckbox
UIFactory <|.. WindowsUIFactory
UIFactory <|.. MacUIFactory
Button <|.. WindowsButton
Button <|.. MacButton
Checkbox <|.. WindowsCheckbox
Checkbox <|.. MacCheckbox
WindowsUIFactory ..> WindowsButton : creates
WindowsUIFactory ..> WindowsCheckbox : creates
MacUIFactory ..> MacButton : creates
MacUIFactory ..> MacCheckbox : creates抽象工厂就是把"造一整套"的能力封装到一个工厂里:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public interface UIFactory {
Button createButton();
Checkbox createCheckbox();
}
public class WindowsUIFactory implements UIFactory {
@Override public Button createButton() { return new WindowsButton(); }
@Override public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
public class MacUIFactory implements UIFactory {
@Override public Button createButton() { return new MacButton(); }
@Override public Checkbox createCheckbox() { return new MacCheckbox(); }
}
|
调用方只拿 UIFactory,造出来的所有控件天然属于同一族:
1
2
3
4
5
6
7
8
9
10
11
| public class App {
private final UIFactory ui;
public App(UIFactory ui) { this.ui = ui; }
public void render() {
Button b = ui.createButton(); // 一定是同一个平台
Checkbox c = ui.createCheckbox();
// ...
}
}
|
抽象工厂 vs 工厂方法
最容易混淆的就是这两兄弟,记住一句话就够:
工厂方法关心"一种产品的多种实现",抽象工厂关心"多种产品的成套实现"。
抽象工厂的痛点是——产品族里加一个新成员(比如要加个 Slider)时,所有具体工厂都得改。这是它结构性的代价。
横向对比
| 模式 | 是否 GoF | 解决的问题 | 新增类型成本 | 典型场景 |
|---|
| 简单工厂 | ✗ | 创建逻辑收拢 | 改工厂方法(违反 OCP) | 类型少、且不常变的场景 |
| 静态工厂方法 | ✗ | 替代构造器 | 加一个静态方法 | 几乎所有库的对象创建 |
| 工厂方法 | ✓ | 把"造哪个"也抽象掉 | 新增产品类 + 新增工厂类(OCP) | 插件化、可扩展的运行时切换 |
| 抽象工厂 | ✓ | 一族产品配套使用 | 新增成员要改所有工厂 | 跨平台、多环境、多套配套实现 |
工程上怎么选
不要看见"工厂"就上工厂方法/抽象工厂。多数业务场景里,越简单越好:
- 单纯想让对象创建更优雅、更有表达力:静态工厂方法(
of、from)就够了,不需要任何工厂类。 - 类型种类有限、变化频率低:简单工厂足够。承担违反 OCP 的代价换简洁,是合理的工程取舍。
- 类型会持续扩展、且希望"加新类型不动老代码":用工厂方法,配合 IoC 容器或 SPI 机制。
- 要支持"多套互不相容的实现族":才考虑抽象工厂。它的复杂度不低,没真实需求别上。
一个常被忽略的现实是——Spring/Guice 这类 IoC 容器,本身就是一个超级抽象工厂。在用 Spring 的项目里,绝大多数"工厂模式"的需求其实可以通过 @Bean + @Qualifier 或 Map<String, XxxStrategy> 注入直接解决,不必手写工厂类。
小结
把四个工厂的本质用一句话压缩:
工厂模式的本质,是把"用什么"和"怎么造"拆开。
四种形态只是抽象程度不同:
- 简单工厂:拆得最浅,调用方还要自己传"类型标识"。
- 静态工厂方法:把"造"封装进类自己,对外只暴露语义化的命名。
- 工厂方法:把"造哪个"也抽象成接口,新增类型不再修改老代码。
- 抽象工厂:把"造一整套"抽象成接口,保证产品族内部一致性。
工程上永远从最简单的开始,业务复杂度真实驱动你时再向上升级——这才是设计模式正确的用法。