Factory Design Pattern
工厂模式详解
工厂模式是一种创建型设计模式:不直接用
new构造对象,而是通过一个"工厂"方法/类来创建。根据复杂度分为简单工厂、工厂方法、抽象工厂三种。
一、它到底解决什么问题
核心矛盾:"创建谁"的逻辑和"使用谁"的逻辑纠缠在一起。
// 混乱的代码:创建逻辑散落在各个业务代码里
public class OrderService {
public void pay(Order order, String type) {
// 创建逻辑泄漏到了业务代码
if ("alipay".equals(type)) {
Payment p = new Alipay(new Config(...), new Logger(...));
} else if ("wechat".equals(type)) {
Payment p = new Wechat(new Config(...), new Logger(...));
}
p.pay(order);
}
}
问题有三:
- 重复:每个用到支付的地方都要写一遍
if-else和构造逻辑 - 脆弱:新增一个支付方式,所有调用点都要改,容易漏改
- 反向依赖:业务代码依赖了具体类(
Alipay),想替换/测试都困难
工厂的解决思路:把"选谁、怎么构造"封装起来,业务代码只面向 Payment 接口。
二、三种形态(按复杂度递进)
1. 简单工厂(Simple Factory)—— 不是 GoF 23 种之一,但最常用
public class PaymentFactory {
public static Payment create(String type) {
if ("alipay".equals(type)) return new Alipay();
if ("wechat".equals(type)) return new Wechat();
throw new IllegalArgumentException("unknown: " + type);
}
}
- 优点:最简单,够用就上
- 缺点:工厂本身违背开闭原则 —— 加新类型还是要改
PaymentFactory - 适用:产品种类少且稳定、调用方多需要集中收口
2. 工厂方法(Factory Method)—— GoF 正式模式
把"创建"定义成接口,由子类决定实例化哪个类:
public abstract class PaymentFactory {
public final void execute(Order order) { // 模板方法:固定流程
Payment p = create(); // 创建延迟到子类
p.pay(order);
}
protected abstract Payment create(); // 工厂方法
}
public class AlipayFactory extends PaymentFactory {
protected Payment create() { return new Alipay(); }
}
- 优点:新增产品 = 新增一个子类,完全符合开闭原则,且工厂方法可以和模板方法配合(创建 + 固定流程)
- 缺点:每加一个产品就要加一个工厂类,类爆炸
- 适用:产品族扩展频繁、创建过程需要继承复用的场景
3. 抽象工厂(Abstract Factory)—— 管理"产品族"
工厂方法管的是"一种产品",抽象工厂管的是一组配套的产品:
public interface UiFactory { // 一套风格 = 一个产品族
Button createButton();
Dialog createDialog();
}
public class DarkThemeFactory implements UiFactory { // 深色系
public Button createButton() { return new DarkButton(); }
public Dialog createDialog() { return new DarkDialog(); }
}
public class LightThemeFactory implements UiFactory { // 浅色系
public Button createButton() { return new LightButton(); }
public Dialog createDialog() { return new LightDialog(); }
}
- 解决:产品之间必须配套使用的问题。换主题时整体换,不会出现"深色按钮 + 浅色弹窗"的混搭
- 优点:保证一致性,符合开闭
- 缺点:加一种新产品(如
Slider),所有工厂都要改 - 典型:
java.sql.DriverManager、Spring 的AbstractApplicationContext
三、对工厂方法的常见疑问:选择逻辑不是还留在调用方吗?
问题:工厂方法一个类型一个子类,如果有多个类型则有多个子类,但使用的地方是不是仍然需要 if 硬判断传入哪个工厂子类?相当于把耦合逻辑提到调用方了吧?
答:这个观察很准确,但这正好是理解工厂方法的关键——它解决的不是"选哪个",而是"怎么创建 + 谁来决定"。
1. 选择逻辑确实没消失,但位置变了
选择从"业务代码"挪到"装配点"(Composition Root)。
没有工厂时:
// 业务代码里到处都有 if
public void a() { Payment p = type.equals("ali") ? new Alipay() : new Wechat(); }
public void b() { Payment p = type.equals("ali") ? new Alipay() : new Wechat(); }
// 改一次,N 处都要动
工厂方法后,选择集中到唯一入口:
// 整个系统只有这一处 if —— 这是"装配"而非"业务"
public PaymentFactory choose(String type) {
return type.equals("ali") ? new AlipayFactory() : new WechatFactory();
}
// 业务代码:不再出现任何类型判断
public void a(PaymentFactory f) { f.execute(order); }
2. 依赖的对象变了:具体产品 → 抽象工厂
简单工厂的 if-else 让工厂内部依赖了具体类(Alipay);工厂方法把选择上移后,业务代码依赖的是 PaymentFactory 抽象——耦合从"业务的每个角落"收敛为"唯一的装配处",加新类型只改装配点。
3. 工厂方法真正的价值不在"选择"
它解决的是另外两个问题:
- 创建过程本身复杂:
AlipayFactory里可以组装配置、日志、连接池,这些细节不再泄漏到业务 - 与模板方法配合:基类定死流程,子类只管"造什么对象"——选择权下放给了子类,调用方根本不用选
4. 什么时候工厂方法不适用
如果你的场景是运行时按 type 字符串动态选(比如支付类型来自前端),那工厂方法确实尴尬——选择点还是得 if。这种场景正解是注册表 + Map,由容器把选择和创建一起收走,调用方一行判断都没有:
// 调用方完全不接触任何 if/工厂子类
Payment p = PaymentRegistry.get(type); // DI 容器注入,类型映射藏在注册表里
结论:"选择逻辑留在调用方"是刻意为之:工厂方法的适用前提是选择在编译期/装配期就固定(每个调用方明确知道自己要什么),此时 if 只出现一次;如果是运行时动态选择,那应该用注册表/DI 容器,而不是工厂方法。判断标准:选择时机是"装配期"还是"运行期"。
四、什么时候不要用工厂
工厂不是银弹,用错反而添乱:
| 场景 | 建议 |
|------|------|
| 只有一个实现类,或构造简单 | 直接用 new,别为了模式而模式 |
| 无多态需求(不需要接口) | 不需要工厂 |
| 类型无限多(如用户自定义插件) | 工厂 + 注册表/反射/Map 更合适 |
| 需要按配置动态选择 | 工厂结合配置中心 / SPI 机制 |
五、进阶:工厂 + 注册表(生产中最常见的形态)
if-else 会随着类型增多越来越长,生产代码通常用注册表代替分支:
@Component
public class PaymentFactory {
private final Map<String, Payment> registry;
public PaymentFactory(List<Payment> all) { // Spring 自动注入所有实现
registry = all.stream()
.collect(Collectors.toMap(Payment::getType, p -> p));
}
public Payment create(String type) {
Payment p = registry.get(type);
if (p == null) throw new IllegalArgumentException("unknown: " + type);
return p;
}
}
加新支付方式 = 加一个 @Component 实现类,工厂代码一行都不用改。这是 Spring/Guice 里最标准的工厂写法。
六、一句话总结
工厂模式 = 把"创建对象"这个变化点封装起来,让调用方面向接口编程。简单工厂管分支,工厂方法管子类扩展,抽象工厂管产品族配套;生产落地时用注册表消除
if-else,配合 DI 容器实现零侵入扩展。