什么是工厂模式?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 则是把"如何创建复杂对象"交给用户实现,形态上接近工厂方法。
小结
- 简单工厂:一个方法造所有产品,简单但违反开闭,可用注册表改良。
- 工厂方法:产品与工厂一一对应,横向扩展零修改,类数量翻倍。
- 抽象工厂:一族多产品配套创建,新增"产品等级"困难。
- 选择依据就一句:只有一个产品维度用工厂方法,需要成套产品用抽象工厂,产品少而稳定用简单工厂。