工厂模式三兄弟对比

一个 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 简单工厂的威力

好处说明
封装创建逻辑对象创建集中在 Factory 中,不再散落各处
解耦使用方调用方只依赖接口和工厂方法,不依赖具体类
统一替换实现测试时可以用 mock 工厂返回测试实例
集中配置创建逻辑可以加缓存、加日志、加监控

1.3 违反 OCP 吗?

这就是面试高频追问:简单工厂其实违反开闭原则!

// 每加一个新支付方式,都要改工厂类
public static Payment createPayment(String type) {
    if ("applepay".equals(type)) { // 新增:必须修改这个方法
        return new ApplePayPayment();
    }
    // ...
}

但这不一定是问题。 如果你的产品处于快速迭代期,支付渠道频繁新增,简单工厂反而比过度工程的抽象工厂更实用。过度设计的危害往往大于设计不足。

【架构权衡】 简单工厂适合:产品线稳定、扩展频率低(几个月才加一个)、团队小。抽象工厂适合:产品线多、扩展频繁、需要严格遵守 OCP 的场景。不要为了"OOP 正确性"而过度设计。

1.4 工厂方法 vs 简单工厂

维度简单工厂工厂方法
符合 OCP❌ 违反,新增产品要改工厂✅ 符合,新增产品只需加新工厂
代码量多(每个产品对应一个工厂类)
适用场景产品种类少、稳定产品种类多、经常变化
维护成本低(集中在一处)分散(需要维护多个工厂)

二、工厂方法模式(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 高频追问

  1. "简单工厂违反 OCP,为什么还要用它?" —— 要说出过度工程的风险,以及简单场景下简单解法更实用的权衡。
  2. "抽象工厂的缺点是什么?" —— 必问。要说出新增产品等级需要改所有工厂,以及这个缺点的应对策略(Builder 辅助、反射+配置等)。
  3. "Spring 为什么还要 FactoryBean?" —— 因为 BeanFactory 创建的是简单对象,但 SqlSessionFactory 等是复杂对象,需要更精细的创建控制。