工厂模式三兄弟对比
一个 if-else 的代价
2019年,我们支付团队有个类叫 PaymentProcessor,里面全是 if-else:
public class PaymentProcessor {
public void process(String type, PaymentRequest request) {
if ("alipay".equals(type)) {
// 支付宝处理
} else if ("wechat".equals(type)) {
// 微信支付
} else if ("unionpay".equals(type)) {
// 银联
} else if ("creditcard".equals(type)) {
// 信用卡
} else if ("banktransfer".equals(type)) {
// 银行转账
}
// 每加一个渠道,这里就多一个 else if
// 测试要改这里,上线要改这里,并发冲突最激烈的也是这里
}
}
三年下来,这个方法积累了 47 个 else if,git blame 追溯到最后已经不知道是谁加的。团队想重构,但每次需求一来,又有人往里面塞 if。
这就是工厂模式要解决的问题:把"创建对象的逻辑"从使用对象的逻辑中分离出来。
一、简单工厂(Static Factory)🔴
1.1 最基本的写法
public class PaymentFactory {
public static Payment createPayment(String type) {
switch (type) {
case "alipay":
return new AlipayPayment();
case "wechat":
return new WechatPayPayment();
case "unionpay":
return new UnionPayPayment();
default:
throw new IllegalArgumentException("Unknown payment type: " + type);
}
}
}
使用方不再需要知道具体类名:
public class OrderService {
public void pay(Order order) {
// 使用方只知道 Payment 接口,不知道具体实现
Payment payment = PaymentFactory.createPayment(order.getPaymentType());
payment.pay(order.getAmount());
}
}
1.2 简单工厂的威力
1.3 违反 OCP 吗?
这就是面试高频追问:简单工厂其实违反开闭原则!
// 每加一个新支付方式,都要改工厂类
public static Payment createPayment(String type) {
if ("applepay".equals(type)) { // 新增:必须修改这个方法
return new ApplePayPayment();
}
// ...
}
但这不一定是问题。 如果你的产品处于快速迭代期,支付渠道频繁新增,简单工厂反而比过度工程的抽象工厂更实用。过度设计的危害往往大于设计不足。
【架构权衡】
简单工厂适合:产品线稳定、扩展频率低(几个月才加一个)、团队小。抽象工厂适合:产品线多、扩展频繁、需要严格遵守 OCP 的场景。不要为了"OOP 正确性"而过度设计。
1.4 工厂方法 vs 简单工厂
二、工厂方法模式(Factory Method)🔴
2.1 核心思想
把工厂也抽象出来——不再是"一个工厂生产所有东西",而是"每种产品有自己的工厂"。
2.2 工厂方法结构
// 产品接口
interface Payment {
void pay(double amount);
}
// 具体产品
class AlipayPayment implements Payment {
public void pay(double amount) {
System.out.println("Alipay: " + amount);
}
}
class WechatPayPayment implements Payment {
public void pay(double amount) {
System.out.println("Wechat Pay: " + amount);
}
}
// 工厂接口
interface PaymentFactory {
Payment createPayment();
}
// 具体工厂
class AlipayFactory implements PaymentFactory {
public Payment createPayment() {
return new AlipayPayment();
}
}
class WechatPayFactory implements PaymentFactory {
public Payment createPayment() {
return new WechatPayPayment();
}
}
使用方式变了:
public class OrderService {
private final PaymentFactory paymentFactory;
// 构造器注入 —— 不依赖具体工厂
public OrderService(PaymentFactory paymentFactory) {
this.paymentFactory = paymentFactory;
}
public void pay(Order order) {
Payment payment = paymentFactory.createPayment();
payment.pay(order.getAmount());
}
}
2.3 工厂方法的优势
新增支付渠道时:
- 简单工厂:改 PaymentFactory.createPayment()
- 工厂方法:新增 AlipayFactory 类(不需要改任何现有代码)
这就是对扩展开放、对修改关闭的含义。
2.4 JDK 中的工厂方法
Collection 接口的 iterator() 方法就是工厂方法的经典应用:
public interface Collection<E> {
Iterator<E> iterator(); // 工厂方法
}
// ArrayList
public Iterator<E> iterator() {
return new Itr(); // 每次调用都返回新实例
}
// HashSet
public Iterator<E> iterator() {
return new HashIterator();
}
Collection 的使用者不需要知道底层用的是数组还是链表,iterator() 方法就是工厂——把"创建迭代器"的逻辑封装在集合内部,对外只暴露接口。
三、抽象工厂模式(Abstract Factory)🔴
3.1 问题的升级
工厂方法解决了"一种产品线"的问题。但如果我需要的是一组相关的产品呢?
比如:一个 UI 框架需要同时支持 Windows 和 macOS 两种风格,每种风格都有一组相关组件(按钮、输入框、对话框)。
Windows 风格:WindowsButton + WindowsTextField + WindowsDialog
macOS 风格:MacButton + MacTextField + MacDialog
工厂方法需要 6 个工厂类,而抽象工厂只需要 2 个:
// 抽象工厂:创建一个产品族
interface UIFactory {
Button createButton();
TextField createTextField();
Dialog createDialog();
}
// Windows 产品族工厂
class WindowsUIFactory implements UIFactory {
public Button createButton() { return new WindowsButton(); }
public TextField createTextField() { return new WindowsTextField(); }
public Dialog createDialog() { return new WindowsDialog(); }
}
// macOS 产品族工厂
class MacUIFactory implements UIFactory {
public Button createButton() { return new MacButton(); }
public TextField createTextField() { return new MacTextField(); }
public Dialog createDialog() { return new MacDialog(); }
}
3.2 产品族 vs 产品等级
产品等级(横向)
Button TextField Dialog
产品族(纵向) Windows | WButton WTextF WDialog
macOS | MButton MTextF MDialog
Linux | LButton LTextF LDialog
抽象工厂保证:同一个工厂创建的所有产品属于同一个产品族。Windows 工厂只创建 Windows 组件,不会混出"Windows 按钮 + macOS 输入框"这种怪物。
⚠️
抽象工厂最大的坑:新增产品等级(如新增 Checkbox)需要修改所有工厂类。比如在上面的例子中加入 Checkbox:
interface UIFactory {
Button createButton();
TextField createTextField();
Dialog createDialog();
Checkbox createCheckbox(); // 新增产品等级 —— 所有实现都要改!
}
这就是抽象工厂的缺点:它对"产品等级"的扩展不友好,对"产品族"的扩展友好。选型时要想清楚哪边更稳定。
3.3 工厂方法 vs 抽象工厂
四、Spring 框架中的工厂模式🟡
4.1 BeanFactory —— 工厂方法的实践
Spring 的 BeanFactory 就是工厂方法模式的极致应用:
public interface BeanFactory {
Object getBean(String name) throws BeansException;
Object getBean(String name, Object... args);
<T> T getBean(Class<T> requiredType);
<T> T getBean(Class<T> requiredType, Object... args);
}
ApplicationContext 是它的扩展(同时也是工厂方法的体现)。每一种 Spring Boot 自动配置,都可能注册自己的 BeanFactory 实现。
4.2 FactoryBean —— 复杂对象的工厂
public interface FactoryBean<T> {
// 创建的对象
T getObject();
// 创建的对象类型
Class<?> getObjectType();
// 是否单例
default boolean isSingleton() {
return true;
}
}
MyBatis 的 SqlSessionFactoryBean 就是 FactoryBean 的典型实现:
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory() {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource());
factory.setMapperLocations(...);
return factory;
}
}
4.3 抽象工厂在 Spring 中的应用
Spring 的 Environment 接口就是抽象工厂的思路:
public interface Environment extends PropertyResolver {
// 获取 profile
String[] getActiveProfiles();
// 判断 profile 是否激活
boolean acceptsProfiles(String... profiles);
}
不同环境(dev、test、prod)有不同的配置,Spring 通过抽象工厂的思想,让代码只依赖 Environment 接口,不关心具体是哪个环境。
五、三种工厂模式的决策框架
需要创建对象吗?
├── 单一产品线,产品种类少 → 简单工厂
├── 单一产品线,产品种类多/经常变化 → 工厂方法
└── 多个相关产品线(产品族)→ 抽象工厂
│
└── 产品等级稳定,产品族经常变化 → 抽象工厂 ✅
└── 产品等级经常变化 → 考虑别的方案 ❌
【面试官心理】
我问他工厂模式,最想看的是他能不能说清楚"什么时候用哪种"。背书谁都会,但能结合自己项目说出"当时选简单工厂是因为产品线稳定,我们三个月才加一个渠道"——这才是真正做过的候选人。
六、生产避坑清单
6.1 简单工厂的常见错误
// ❌ 错误:在工厂里加业务逻辑
public static Payment createPayment(String type) {
Payment payment = null;
if ("alipay".equals(type)) {
payment = new AlipayPayment();
payment.init(); // 业务逻辑不该在工厂里
}
return payment;
}
// ✅ 正确:工厂只负责创建
public static Payment createPayment(String type) {
switch (type) {
case "alipay":
return new AlipayPayment();
// ...
}
}
6.2 工厂方法的常见错误
// ❌ 错误:工厂类直接 new 具体产品
class AlipayFactory implements PaymentFactory {
public Payment createPayment() {
return new AlipayPayment(); // 违反 DIP
}
}
// ✅ 正确:如果需要可配置,通过 setter 注入或构造函数注入
class AlipayFactory implements PaymentFactory {
private PaymentConfig config;
public void setConfig(PaymentConfig config) {
this.config = config;
}
public Payment createPayment() {
return new AlipayPayment(config);
}
}
6.3 抽象工厂的常见错误
// ❌ 错误:在一个工厂里混入不同产品族
class MixedFactory implements UIFactory {
public Button createButton() { return new WindowsButton(); } // Windows
public TextField createTextField() { return new MacTextField(); } // macOS?!?
// 这个工厂创建的产品族不统一
}
💡
抽象工厂的核心约束:一个工厂实例创建的所有产品必须属于同一个产品族。如果你的工厂创建了 Windows 按钮和 macOS 输入框,说明你的设计有根本问题。
七、工程代价评估
八、面试总结
8.1 追问链
第一层:三种工厂模式的写法
→ 能写出代码
第二层:三种模式的区别
→ 能说出适用场景
第三层:JDK/Spring 中的实际应用
→ 能举例 BeanFactory、FactoryBean
第四层:什么时候不用工厂模式
→ 能说出简单对象直接 new 即可
→ 能说出 DI 容器已经解决了工厂问题
8.2 高频追问
- "简单工厂违反 OCP,为什么还要用它?" —— 要说出过度工程的风险,以及简单场景下简单解法更实用的权衡。
- "抽象工厂的缺点是什么?" —— 必问。要说出新增产品等级需要改所有工厂,以及这个缺点的应对策略(Builder 辅助、反射+配置等)。
- "Spring 为什么还要 FactoryBean?" —— 因为 BeanFactory 创建的是简单对象,但 SqlSessionFactory 等是复杂对象,需要更精细的创建控制。