什么是工厂模式?3种实现方式的区别和特点?

#中间件#面试八股

什么是工厂模式?3种实现方式的区别和特点?

结论先行:工厂模式解决的是"对象的创建与使用分离"。三种形态层层递进:简单工厂(一个方法里 switch 造所有产品,不算 GoF 23 种)、工厂方法(每个产品一个工厂,靠新增子类扩展)、抽象工厂(一个工厂造一族相关产品,如"一整套皮肤")。

1. 先分清"产品维度":这是理解三者的钥匙

                  产品等级(同一类东西的不同实现)
 ┌────────────┬──────────┬──────────┬──────────┐
 │            │  支付宝  │  微信    │  银联    │
 ├────────────┼──────────┼──────────┼──────────┤
 │ 扫码支付   │ AlipayQr │ WxQr     │ UnionQr  │  ← 产品族=某一家的全套能力
 │ 退款       │ AliRef   │ WxRef    │ UnionRef │
 │ 对账单     │ AliBill  │ WxBill   │ UnionBil │
 └────────────┴──────────┴──────────┴──────────┘
  • 工厂方法:一个维度(只要"支付"),每个实现一个工厂。
  • 抽象工厂:两个维度(支付方式 × 能力),一个工厂产出同一族的多个产品,保证不同族的产品不会被混用。

2. 三种实现的对比

维度简单工厂工厂方法抽象工厂
结构一个类 + switch/if抽象工厂 + N 个具体工厂抽象工厂 + N 个族工厂,每族多产品
新增一个产品改工厂代码(违反开闭)新增工厂类(符合开闭)新增族工厂(符合开闭)
新增一个产品等级改工厂所有工厂都要改所有工厂都要改(违反开闭)
客户端依赖工厂 + 产品枚举抽象工厂 + 抽象产品抽象工厂 + 多个抽象产品
适用产品少且稳定单一产品维度、扩展频繁成套产品、必须成组使用

3. 简单工厂

public class PayFactory {
    public static PayChannel create(String type) {
        switch (type) {
            case "alipay": return new AlipayChannel();
            case "wechat": return new WechatChannel();
            case "union":  return new UnionChannel();
            default: throw new IllegalArgumentException("未知支付方式: " + type);
        }
    }
}

PayChannel channel = PayFactory.create(config.getPayType());   // 使用:只认识工厂和接口
channel.pay(amount);

优点:客户端只认识一个静态方法;缺点是加渠道必须改 switch。改良版把 switch 换成注册表,新增渠道只注册:

public class PayFactory {
    private static final Map<String, Supplier<PayChannel>> REG = new ConcurrentHashMap<>();

    static {
        REG.put("alipay", AlipayChannel::new);
        REG.put("wechat", WechatChannel::new);
    }

    // 三方渠道可自行注册,新增渠道不用改这个类
    public static void register(String type, Supplier<PayChannel> supplier) { REG.put(type, supplier); }

    public static PayChannel create(String type) {
        Supplier<PayChannel> s = REG.get(type);
        if (s == null) throw new IllegalArgumentException("未知支付方式: " + type);
        return s.get();
    }
}

4. 工厂方法

interface PayChannel {
    void pay(long amount);
}

interface PayChannelFactory {
    PayChannel create();                          // 工厂接口,与产品一一对应
}

class AlipayFactory implements PayChannelFactory {
    public PayChannel create() { return new AlipayChannel(); }
}

class WechatFactory implements PayChannelFactory {
    public PayChannel create() { return new WechatChannel(); }
}

// 客户端依赖抽象,具体工厂由配置/注册中心注入
class PayService {
    private final PayChannelFactory factory;
    PayService(PayChannelFactory factory) { this.factory = factory; }

    void doPay(long amount) {
        factory.create().pay(amount);             // 不知道也不关心是哪个实现
    }
}

新增"数字人民币"渠道:加一个 DcepChannel 和一个 DcepFactory,PayService 一行不改——这才是开闭原则的样子。

5. 抽象工厂

// 两个产品等级:支付、对账;一个族工厂造同一家服务商的全套能力
interface QrPay { void pay(long amount); }
interface Bill  { void download(String month); }

interface PaySuiteFactory {
    QrPay createPay();
    Bill  createBill();
}

class AlipaySuite implements PaySuiteFactory {
    public QrPay createPay()  { return new AlipayQr(); }
    public Bill  createBill() { return new AlipayBill(); }
}
// WechatSuite 同理:支付出 WechatQr、对账出 WechatBill

价值在于保证同一族产品被配套使用:拿到 AlipaySuite,支付和对账一定都来自支付宝,不会出现"用微信支付、查支付宝账单"的错配。

6. 常见追问

Q:简单工厂违反开闭原则,为什么还在用? 产品种类少且稳定时,switch 的可读性优于一堆工厂类;真要扩展,用上面的注册表方案即可消除修改。严格说它不属于 GoF 的 23 种,只是工厂方法的简化变体。

Q:抽象工厂能不能新增产品等级? 很难。加一个"退款"能力,所有族工厂都要实现新方法——这是抽象工厂的固有短板。所以它适合"产品族稳定、族内产品会增删"的场景。

Q:Spring 的 BeanFactory 属于哪一种? 更接近简单工厂 + 配置驱动:按名字/类型从容器取,创建逻辑由配置和反射决定。FactoryBean 则是把"如何创建复杂对象"交给用户实现,形态上接近工厂方法。

小结

  • 简单工厂:一个方法造所有产品,简单但违反开闭,可用注册表改良。
  • 工厂方法:产品与工厂一一对应,横向扩展零修改,类数量翻倍。
  • 抽象工厂:一族多产品配套创建,新增"产品等级"困难。
  • 选择依据就一句:只有一个产品维度用工厂方法,需要成套产品用抽象工厂,产品少而稳定用简单工厂。
笔记加载中…