工厂模式三兄弟:简单工厂、工厂方法、抽象工厂
new一个对象有什么错?问题不在new,而在于把"用哪个类"这个决定硬编码进了调用方。工厂模式就是把"创建"这件事从"使用"里拆出来——这篇文章把三个最容易混淆的"工厂"讲清楚:简单工厂(不在 GoF 23 里,却最常用)、工厂方法、抽象工厂,并给出可运行的单文件 Java 示例、UML 类图与选型判断。
一、为什么需要工厂:new 到底错在哪
假设下单后要发起支付:
public void checkout(String type, BigDecimal amount) {
if ("alipay".equals(type)) {
new AliPay().pay(amount);
} else if ("wechat".equals(type)) {
new WeChatPay().pay(amount);
} else if ("paypal".equals(type)) {
new PayPalPay().pay(amount);
}
}
这段代码能跑,但它的三处病根很典型:
- 耦合:调用方(业务逻辑)依赖了所有具体实现类。加一种支付方式,就要改这段业务代码。
- 重复:这种
if-else会在下单、退款、对账……每个入口复制一遍。 - 职责混乱:业务方法本该只关心"收钱",却在操心"怎么造出支付对象"(可能还要读配置、初始化 SDK、处理线程池)。
工厂模式的一句话概括:把"创建哪个具体类"的决策,从调用方搬到工厂里,让调用方只面向接口编程。
二、三个"工厂"是什么关系
| 名称 | 关注点 | 是否 GoF 23 模式 | 一句话记忆 |
|---|---|---|---|
| 简单工厂(静态工厂) | 一个工厂造多种产品 | ❌ 不在(习惯叫法) | 一个柜台,报名字给货 |
| 工厂方法 | 一个工厂造一种产品,交给子类决定具体类型 | ✅ | 一条产品线一个工厂 |
| 抽象工厂 | 一个工厂造一族(多个相关)产品 | ✅ | 成套供货,同风格不混搭 |
这里有个关键区分:"产品等级结构"和"产品族"——记住这两个词,后面就再也不会混。
- 产品等级结构:
Button是抽象产品,WinButton、MacButton属于同一等级结构的实现。 - 产品族:
WinButton + WinCheckbox + WinTextBox这一整套属于"Windows 风格"族。
工厂方法解决"一个产品等级结构的创建延迟到子类";抽象工厂解决"多个产品等级结构成套创建、且要保证同一族"。
三、简单工厂:最常用,也最容易被误用
它不藏在复杂的抽象里,核心只有一个静态方法。

/** 简单工厂:一个方法决定造哪个具体类 */
public class SimpleFactoryDemo {
interface Payment { // 抽象产品
void pay(java.math.BigDecimal amount);
}
static class AliPay implements Payment { // 具体产品 A
public void pay(java.math.BigDecimal amount) {
System.out.println("支付宝支付 " + amount);
}
}
static class WeChatPay implements Payment { // 具体产品 B
public void pay(java.math.BigDecimal amount) {
System.out.println("微信支付 " + amount);
}
}
/** 工厂:集中创建逻辑,调用方只认接口 */
static class PaymentFactory {
static Payment create(String type) {
switch (type.toLowerCase()) {
case "alipay": return new AliPay();
case "wechat": return new WeChatPay();
default: throw new IllegalArgumentException("不支持的支付方式: " + type);
}
}
}
public static void main(String[] args) {
Payment p = PaymentFactory.create("alipay");
p.pay(new java.math.BigDecimal("19.90"));
PaymentFactory.create("wechat").pay(new java.math.BigDecimal("5.00"));
}
}
优点:简单直接、调用方零耦合、把创建逻辑(含初始化、异常处理)集中到一处。
缺点:新增产品必须修改工厂类(那个 switch),违背开闭原则;工厂类会随着产品增多而膨胀。
什么时候用它:产品种类少且稳定、不会频繁增加;创建逻辑需要统一收口(校验、缓存、默认值)。
JDK 里的简单工厂:
Integer.valueOf(int)、Boolean.valueOf(boolean)、Calendar.getInstance()、Charset.forName("UTF-8")、Executors.newFixedThreadPool(n)。这些都是静态方法返回对象,和你自己写的工厂本质相同。
顺手升级:用"注册表"替掉 switch
switch 的问题在于"新增产品要动工厂"。用 EnumMap + Supplier(Java 8+)可以把"注册"和"创建"分开,工厂再也不用改:
/** 注册表式工厂:新增产品只注册,不改工厂 */
public class RegistryFactoryDemo {
interface Payment { void pay(java.math.BigDecimal amount); }
static class AliPay implements Payment {
public void pay(java.math.BigDecimal a) { System.out.println("支付宝 " + a); }
}
static class WeChatPay implements Payment {
public void pay(java.math.BigDecimal a) { System.out.println("微信 " + a); }
}
static class PaymentFactory {
private static final java.util.Map<String, java.util.function.Supplier<Payment>>
REGISTRY = new java.util.HashMap<>();
static void register(String type, java.util.function.Supplier<Payment> supplier) {
REGISTRY.put(type.toLowerCase(), supplier);
}
static Payment create(String type) {
java.util.function.Supplier<Payment> s = REGISTRY.get(type.toLowerCase());
if (s == null) throw new IllegalArgumentException("未注册的支付方式: " + type);
return s.get();
}
}
public static void main(String[] args) {
PaymentFactory.register("alipay", AliPay::new); // 注册
PaymentFactory.register("wechat", WeChatPay::new);
PaymentFactory.create("alipay").pay(new java.math.BigDecimal("9.90"));
}
}
这其实是很多框架的做法:SPI、ServiceLoader、Spring 的 BeanFactory 都是"注册 + 按名取"。简单工厂的形态,注册表的骨架。
四、工厂方法:把"用哪个类"交给子类
典型场景:物流公司要发货,但运输方式由具体业务线决定(陆运算卡车、海运算轮船)。

/** 工厂方法:抽象工厂定义"造什么"的接口,由子类决定具体产品 */
public class FactoryMethodDemo {
interface Transport { // 抽象产品
void deliver();
}
static class Truck implements Transport {
public void deliver() { System.out.println("卡车陆运,走高速"); }
}
static class Ship implements Transport {
public void deliver() { System.out.println("轮船海运,走港口"); }
}
/** 抽象工厂:包含"业务方法"和"工厂方法" */
abstract static class Logistics {
protected abstract Transport createTransport(); // 工厂方法
public void planDelivery() { // 业务方法:只依赖抽象产品
Transport t = createTransport();
System.out.println("规划路线…");
t.deliver();
}
}
static class RoadLogistics extends Logistics { // 具体工厂 A
protected Transport createTransport() { return new Truck(); }
}
static class SeaLogistics extends Logistics { // 具体工厂 B
protected Transport createTransport() { return new Ship(); }
}
public static void main(String[] args) {
new RoadLogistics().planDelivery();
new SeaLogistics().planDelivery();
}
}

与简单工厂的本质区别:简单工厂的工厂类自己判断并创建;工厂方法把 createTransport() 声明成抽象方法,由子类实现——新增一种运输方式,只需要新增一个工厂子类,不用改任何已有代码。
代价:类数量翻倍(每个产品配一个工厂)。所以它适合"产品种类会持续增加"的场景。
JDK 里的工厂方法:
Collection.iterator()——ArrayList返回自己的迭代器,LinkedList返回自己的,接口只声明"能造迭代器";再比如URLStreamHandlerFactory、NumberFormat.getInstance()在子类体系中的实现。
五、抽象工厂:成套供货,保证同族一致
最经典的例子是跨平台 UI:一个界面要同时用"按钮 + 复选框 + 文本框",它们必须是同一风格,不能 Windows 按钮配 Mac 复选框。

/** 抽象工厂:一个工厂负责一个产品族(同风格的整套组件) */
public class AbstractFactoryDemo {
interface Button { void paint(); } // 产品等级结构 1
interface Checkbox { void paint(); } // 产品等级结构 2
static class WinButton implements Button {
public void paint() { System.out.println("渲染 Windows 风格按钮"); }
}
static class WinCheckbox implements Checkbox {
public void paint() { System.out.println("渲染 Windows 风格复选框"); }
}
static class MacButton implements Button {
public void paint() { System.out.println("渲染 macOS 风格按钮"); }
}
static class MacCheckbox implements Checkbox {
public void paint() { System.out.println("渲染 macOS 风格复选框"); }
}
/** 抽象工厂:声明一族产品的创建方法 */
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
static class WinFactory implements GUIFactory {
public Button createButton() { return new WinButton(); }
public Checkbox createCheckbox() { return new WinCheckbox(); }
}
static class MacFactory implements GUIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
/** 客户端只依赖抽象工厂 + 抽象产品,不知道具体风格 */
static class Application {
private final Button button;
private final Checkbox checkbox;
Application(GUIFactory factory) {
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
void render() { button.paint(); checkbox.paint(); }
}
public static void main(String[] args) {
String os = System.getProperty("os.name").toLowerCase();
GUIFactory factory = os.contains("mac") ? new MacFactory() : new WinFactory();
new Application(factory).render();
}
}
它换来了什么:新增一套风格(比如 Linux 风格)只需新增一个工厂 + 两个产品类,客户端代码不动;而且不同族的组件绝不会混搭——这个一致性保证是抽象工厂独有的价值。
它的软肋(面试常问):新增"产品种类"(比如要加 TextBox)要改所有工厂接口和所有实现。也就是说:
- 维度是"族"(换风格)→ 抽象工厂符合开闭原则;
- 维度是"种类"(加组件)→ 抽象工厂违背开闭原则,这时候要拆成更细的工厂方法或改用注册表。
框架里的抽象工厂:
javax.xml.parsers.DocumentBuilderFactory(不同 XML 解析器实现)、JDBC 的Driver→Connection→Statement一整套(换数据库只换驱动)、Spring 的FactoryBean也算半个。
六、三者对比与选型
| 维度 | 简单工厂 | 工厂方法 | 抽象工厂 |
|---|---|---|---|
| 抽象层次 | 无(具体工厂) | 抽象工厂 + 具体工厂 | 抽象工厂 + 具体工厂 |
| 产品数量 | 一个等级结构,多个实现 | 一个等级结构,延迟到子类 | 多个等级结构(产品族) |
| 新增产品实现 | ❌ 要改工厂 | ✅ 新增工厂子类 | ✅ 新增族(但要改接口才能加种类) |
| 类数量 | 少 | 多(产品数 × 2) | 最多(族数 × 种类数) |
| 典型场景 | 支付/日志/解析器按名创建 | 一个业务线一种实现 | 跨平台 UI、多数据库套装 |

判断口诀:一个维度用工厂方法,两个(以上)维度用抽象工厂;产品少而稳定,简单工厂最实用。
七、什么时候不要用工厂
设计模式的价值在于隔离变化,如果没有变化点,它就是纯粹的复杂度:
- 只有一个实现、短期也没有第二个:直接
new,或者用依赖注入把它注入进来。 - 创建逻辑就是一句
new:包一层工厂只是把new换了个地方,没有任何收益。 - 已经有 DI 容器:Spring 里
@Component+ 构造器注入基本取代了手写工厂;ApplicationContext本身就是个"超级工厂"(BeanFactory是它的顶层接口)。这时候你要写的是@Configuration+@Bean,而不是自定义工厂类。 - 只是想"按名字取对象":
EnumMap/Map<String, Supplier<T>>的注册表比一整套继承体系轻得多(见第三节的注册表版)。
八、参考资料
- GoF《设计模式:可复用面向对象软件的基础》——Factory Method、Abstract Factory 两章
- Joshua Bloch《Effective Java》Item 1:静态工厂方法替代构造器(讲的是"工厂"的另一种形态与它的取舍)
- Spring Framework 文档:The IoC Container / BeanFactory 与 ApplicationContext 的关系