工厂方法模式 & 抽象工厂模式¶
一句话记忆口诀:工厂方法"一个产品族,子类决定实例化";抽象工厂"多个产品族,切换整套产品线"。
1. 引入:它解决了什么问题?¶
没有工厂模式时的问题¶
当创建对象的逻辑复杂,或者需要根据条件创建不同类型的对象时,直接 new 会导致调用方与具体实现强耦合:
// ❌ 反例:调用方直接 new,与具体实现强耦合
public class OrderService {
public void createOrder(String payType) {
if ("alipay".equals(payType)) {
AlipayPayment payment = new AlipayPayment(); // 直接依赖具体类
payment.pay(100);
} else if ("wechat".equals(payType)) {
WechatPayment payment = new WechatPayment(); // 直接依赖具体类
payment.pay(100);
}
// 新增支付方式?必须修改 OrderService!违反开闭原则
}
}
问题根因:
1. 调用方(OrderService)与具体实现(AlipayPayment)强耦合
2. 新增支付方式必须修改调用方代码,违反开闭原则
3. 对象创建逻辑散落在各处,难以统一管理
工作中的典型应用场景¶
| 场景 | Spring/JDK 中的例子 |
|---|---|
| 日志框架 | LoggerFactory.getLogger() — 工厂方法 |
| JDBC 驱动 | DriverManager.getConnection() — 工厂方法 |
| Spring BeanFactory | getBean() — 工厂方法 |
| 跨数据库支持 | SqlSessionFactory(MyBatis)— 抽象工厂 |
| GUI 组件 | 不同 OS 创建不同风格的按钮/文本框 — 抽象工厂 |
2. 类比:用生活模型建立直觉¶
工厂方法:奶茶店加盟¶
一家奶茶总部(抽象工厂接口)规定了"制作奶茶"的流程,但具体用什么原料、什么口味由各地加盟店(具体工厂子类)决定。
- 接口/抽象角色:奶茶总部的"制作规范"(
TeaFactory接口) - 具体实现角色:上海加盟店、北京加盟店(
ShanghaiTeaFactory、BeijingTeaFactory) - 调用方:顾客(
Client),只需说"给我一杯奶茶",不关心哪家店做的
抽象工厂:宜家家居套装¶
宜家有"北欧风"和"现代风"两套家居风格(产品族)。每套风格都包含沙发、桌子、椅子(产品等级)。你选定一个风格,就能得到整套配套产品。
- 接口/抽象角色:家居风格工厂(
FurnitureFactory),定义创建沙发、桌子的方法 - 具体实现角色:北欧风工厂、现代风工厂(
NordicFactory、ModernFactory) - 调用方:装修公司(
Client),只需选定风格,不关心具体产品型号
抽象定义¶
工厂方法模式:定义一个创建对象的接口,让子类决定实例化哪个类。工厂方法把实例化推迟到子类。
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。
3. 原理:逐步拆解核心机制¶
工厂方法模式 UML 类图¶
classDiagram
class PaymentFactory {
<<interface>>
+createPayment() Payment
}
class AlipayFactory {
+createPayment() Payment
}
class WechatFactory {
+createPayment() Payment
}
class Payment {
<<interface>>
+pay(amount) void
}
class AlipayPayment {
+pay(amount) void
}
class WechatPayment {
+pay(amount) void
}
class OrderService {
-factory PaymentFactory
+createOrder()
}
PaymentFactory <|.. AlipayFactory
PaymentFactory <|.. WechatFactory
Payment <|.. AlipayPayment
Payment <|.. WechatPayment
AlipayFactory ..> AlipayPayment : creates
WechatFactory ..> WechatPayment : creates
OrderService --> PaymentFactory
工厂方法模式 Java 代码¶
// ===== 产品接口 =====
public interface Payment {
void pay(double amount);
}
// ===== 具体产品 =====
public class AlipayPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("支付宝支付:" + amount + " 元");
}
}
public class WechatPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("微信支付:" + amount + " 元");
}
}
// ===== 工厂接口(核心:定义创建产品的方法)=====
public interface PaymentFactory {
Payment createPayment(); // 工厂方法:子类决定创建哪种产品
}
// ===== 具体工厂(子类决定实例化哪个产品)=====
public class AlipayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
// 可以在这里加入复杂的初始化逻辑(如读取配置、建立连接)
return new AlipayPayment();
}
}
public class WechatFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new WechatPayment();
}
}
// ===== 调用方(只依赖接口,不依赖具体实现)=====
public class OrderService {
private final PaymentFactory factory;
// 通过构造方法注入工厂(依赖注入,Spring 的核心思想)
public OrderService(PaymentFactory factory) {
this.factory = factory;
}
public void createOrder(double amount) {
Payment payment = factory.createPayment(); // 不知道也不关心具体类型
payment.pay(amount);
}
}
// ===== 使用示例 =====
public class Main {
public static void main(String[] args) {
// 新增支付方式只需新增工厂类,不修改 OrderService!
OrderService alipayOrder = new OrderService(new AlipayFactory());
alipayOrder.createOrder(100.0);
OrderService wechatOrder = new OrderService(new WechatFactory());
wechatOrder.createOrder(200.0);
}
}
抽象工厂模式 UML 类图¶
classDiagram
class UIFactory {
<<interface>>
+createButton() Button
+createCheckbox() Checkbox
}
class WindowsFactory {
+createButton() Button
+createCheckbox() Checkbox
}
class MacFactory {
+createButton() Button
+createCheckbox() Checkbox
}
class Button {
<<interface>>
+render() void
}
class Checkbox {
<<interface>>
+render() void
}
class WindowsButton
class MacButton
class WindowsCheckbox
class MacCheckbox
class Application {
-factory UIFactory
+buildUI()
}
UIFactory <|.. WindowsFactory
UIFactory <|.. MacFactory
Button <|.. WindowsButton
Button <|.. MacButton
Checkbox <|.. WindowsCheckbox
Checkbox <|.. MacCheckbox
WindowsFactory ..> WindowsButton
WindowsFactory ..> WindowsCheckbox
MacFactory ..> MacButton
MacFactory ..> MacCheckbox
Application --> UIFactory
抽象工厂模式 Java 代码¶
// ===== 产品接口族 =====
public interface Button {
void render();
}
public interface Checkbox {
void render();
}
// ===== Windows 产品族 =====
public class WindowsButton implements Button {
@Override
public void render() { System.out.println("渲染 Windows 风格按钮"); }
}
public class WindowsCheckbox implements Checkbox {
@Override
public void render() { System.out.println("渲染 Windows 风格复选框"); }
}
// ===== Mac 产品族 =====
public class MacButton implements Button {
@Override
public void render() { System.out.println("渲染 Mac 风格按钮"); }
}
public class MacCheckbox implements Checkbox {
@Override
public void render() { System.out.println("渲染 Mac 风格复选框"); }
}
// ===== 抽象工厂(核心:创建一族相关产品)=====
public interface UIFactory {
Button createButton(); // 创建同族按钮
Checkbox createCheckbox(); // 创建同族复选框
// 同一工厂创建的产品保证风格一致!
}
// ===== 具体工厂(每个工厂负责一个产品族)=====
public class WindowsFactory implements UIFactory {
@Override
public Button createButton() { return new WindowsButton(); }
@Override
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
public class MacFactory implements UIFactory {
@Override
public Button createButton() { return new MacButton(); }
@Override
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
// ===== 调用方(切换整套 UI 风格只需换一个工厂)=====
public class Application {
private final UIFactory factory;
public Application(UIFactory factory) {
this.factory = factory;
}
public void buildUI() {
Button button = factory.createButton();
Checkbox checkbox = factory.createCheckbox();
button.render();
checkbox.render();
// 保证 button 和 checkbox 风格一致!
}
}
核心流程图¶
flowchart TD
subgraph 工厂方法模式
A1[Client] -->|选择工厂| B1[ConcreteFactory]
B1 -->|createProduct| C1[ConcreteProduct]
A1 -->|使用| C1
end
subgraph 抽象工厂模式
A2[Client] -->|选择产品族工厂| B2[ConcreteFactory]
B2 -->|createProductA| C2[ProductA]
B2 -->|createProductB| D2[ProductB]
A2 -->|使用| C2
A2 -->|使用| D2
C2 -.->|同族,风格一致| D2
end
4. 特性:关键对比¶
工厂方法 vs 抽象工厂¶
| 对比维度 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|
| 目的 | 创建一种产品,子类决定具体类型 | 创建一族相关产品,保证产品间兼容 |
| 产品数量 | 一个工厂方法,一种产品 | 多个工厂方法,多种产品(产品族) |
| 扩展方式 | 新增产品:新增工厂子类 | 新增产品族:新增工厂类;新增产品种类:修改所有工厂(违反开闭) |
| 使用时机 | 不知道要创建哪种具体产品时 | 需要保证一组产品的兼容性/一致性时 |
| 典型例子 | LoggerFactory、Spring BeanFactory |
MyBatis SqlSessionFactory、跨平台 UI |
简单工厂 vs 工厂方法 vs 抽象工厂¶
| 模式 | 特点 | 缺点 |
|---|---|---|
| 简单工厂(非 GoF) | 一个工厂类,switch/if 判断 | 新增产品必须修改工厂类,违反开闭原则 |
| 工厂方法 | 每种产品对应一个工厂子类 | 工厂类数量多 |
| 抽象工厂 | 一个工厂创建一族产品 | 新增产品种类需修改所有工厂 |
在 Spring / JDK 中的应用¶
| 框架/类 | 模式类型 | 说明 |
|---|---|---|
BeanFactory.getBean() |
工厂方法 | 根据 Bean 名称/类型创建实例 |
LoggerFactory.getLogger() |
工厂方法 | 返回对应日志实现 |
Calendar.getInstance() |
工厂方法 | 根据 Locale 返回不同 Calendar |
DriverManager.getConnection() |
工厂方法 | 根据 URL 返回对应数据库连接 |
MyBatis SqlSessionFactory |
抽象工厂 | 创建 SqlSession、Executor 等一族对象 |
5. 边界:异常情况与常见误区¶
误区一:简单工厂当工厂方法用(编译期无问题,运行期违反开闭原则)¶
// ❌ 错误:用 if-else 实现"工厂",每次新增类型都要修改这里
public class PaymentFactory {
public static Payment create(String type) {
if ("alipay".equals(type)) return new AlipayPayment();
else if ("wechat".equals(type)) return new WechatPayment();
// 新增支付宝花呗?必须在这里加 else if!
throw new IllegalArgumentException("未知支付类型: " + type);
}
}
// ✅ 正确:用工厂方法模式,新增类型只需新增工厂子类
// 参考上方代码示例
误区二:抽象工厂新增产品种类时忘记修改所有工厂(编译期报错)¶
// ❌ 问题:抽象工厂新增 createScrollbar() 方法后,
// 所有具体工厂都必须实现,否则编译报错
public interface UIFactory {
Button createButton();
Checkbox createCheckbox();
Scrollbar createScrollbar(); // 新增!所有实现类都要改!
}
// 这是抽象工厂的固有缺陷:对"产品族扩展"友好,对"产品种类扩展"不友好
// ✅ 解决方案:提供默认实现(Java 8 default 方法),减少修改范围
public interface UIFactory {
Button createButton();
Checkbox createCheckbox();
default Scrollbar createScrollbar() {
return new DefaultScrollbar(); // 默认实现,子类可选择性覆盖
}
}
误区三:工厂方法和 Spring @Bean 混淆¶
// ❌ 误解:认为 Spring @Bean 就是工厂方法模式
// 实际上 Spring @Bean 更接近"工厂方法"的思想,但 Spring 的核心是 IoC 容器
// ✅ 正确理解:Spring 的 FactoryBean 接口才是工厂方法模式的标准实现
public class MyServiceFactoryBean implements FactoryBean<MyService> {
@Override
public MyService getObject() throws Exception {
// 复杂的创建逻辑
return new MyServiceImpl();
}
@Override
public Class<?> getObjectType() {
return MyService.class;
}
}
6. 总结:面试标准化表达¶
高频面试题¶
Q1:工厂方法模式和抽象工厂模式有什么区别?
工厂方法模式关注单一产品的创建,定义一个创建产品的接口,由子类决定具体实例化哪个类,适合产品种类单一但需要灵活扩展的场景。抽象工厂模式关注一族相关产品的创建,一个工厂接口包含多个工厂方法,保证同一工厂创建的产品相互兼容,适合需要切换整套产品线的场景(如跨平台 UI、多数据库支持)。简单说:工厂方法是"一个产品,多种实现";抽象工厂是"多个产品,成套切换"。
Q2:Spring 中哪里用到了工厂模式?
Spring 大量使用工厂模式:
BeanFactory是工厂方法模式的核心体现,getBean()根据名称/类型返回对应实例;FactoryBean接口允许自定义 Bean 的创建逻辑;LoggerFactory是工厂方法模式;SqlSessionFactory(MyBatis)是抽象工厂模式,负责创建 SqlSession、Executor 等一族对象。工厂模式让 Spring 实现了"面向接口编程",调用方不依赖具体实现类。
Q3:什么时候用工厂模式,什么时候直接 new?
以下情况考虑工厂模式:①创建逻辑复杂(需要初始化、配置、依赖注入);②需要根据条件创建不同类型的对象;③需要对创建过程进行统一管理(如缓存、池化);④需要解耦调用方和具体实现类。如果对象创建简单(一行 new 搞定),且不需要扩展,直接 new 更清晰,不要过度设计。
一句话记忆口诀:工厂方法"一个产品,子类决定实例化";抽象工厂"多个产品,切换整套产品线";Spring 的
BeanFactory和FactoryBean是最好的实战案例。