抽象工厂模式

设计模式笔记:抽象工厂模式 (Abstract Factory Pattern)

一、一句话概括

提供一个接口,用于创建一整套相互关联或相互依赖的对象(一个产品族),而无需指定它们具体的类。

二、为什么需要它 (Why - The Pain Point)

工厂方法模式解决了单个产品的创建问题。但如果我们需要创建的是一系列产品,并且要保证这些产品能相互兼容、协同工作,情况就变得复杂了。

想象一下,你正在开发一个跨平台的 UI 框架,需要支持 Windows 和 macOS 两种操作系统风格。

  • 在 Windows 上,你需要创建 WindowsButton, WindowsCheckbox, WindowsTextField
  • 在 macOS 上,你需要创建 MacOSButton, MacOSCheckbox, MacOSTextField

痛点:

  1. 产品兼容性问题:你绝对不希望在同一个界面上出现一个 Windows 风格的按钮和一个 macOS 风格的复选框。这会造成 UI 风格混乱,用户体验极差。必须保证,一旦选择了某个主题(如 Windows),所有被创建的组件都必须是该主题下的。
  2. 客户端逻辑混乱:如果让客户端自己去组合这些组件,代码会非常复杂且容易出错。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 痛点:客户端需要知道所有具体类,并手动保证风格统一
    if (os.equals("Windows")) {
    button = new WindowsButton();
    checkbox = new WindowsCheckbox();
    } else if (os.equals("macOS")) {
    button = new MacOSButton();
    checkbox = new MacOSCheckbox();
    }
    // ... 大量的 if-else 逻辑,且非常容易出错
    这种代码同样违反了开闭原则,每次新增一个操作系统主题(如 Linux GTK),都需要修改所有创建组件的地方。

核心问题:如何确保创建的一系列对象(一个产品族)都属于同一个“风格”或“系列”,并让客户端与具体的“风格”实现解耦?

三、它是什么 (What - The Solution)

抽象工厂模式通过引入一个 “超级工厂”(抽象工厂)来解决这个问题。这个超级工厂不生产单个产品,而是生产一整个产品族

核心思想和角色:

  1. **抽象工厂 (Abstract Factory)**:
    • 这是一个接口或抽象类。
    • 它声明了一组返回不同抽象产品的工厂方法。例如,createButton()createCheckbox()。它定义了要生产哪些种类的产品。
  2. **具体工厂 (Concrete Factory)**:
    • 实现抽象工厂接口的类。
    • 一个具体工厂对应一个产品族。例如,WindowsFactory 负责生产所有 Windows 风格的组件,MacOSFactory 负责生产所有 macOS 风格的组件。
    • 它实现了抽象工厂中的所有方法,返回具体的、属于自己族的产品实例。
  3. **抽象产品 (Abstract Product)**:
    • 为产品族中的每一种产品定义一个接口。例如,Button 接口,Checkbox 接口。
  4. **具体产品 (Concrete Product)**:
    • 实现抽象产品接口的具体类。
    • 它们被组织成多个产品族。例如,WindowsButtonWindowsCheckbox 属于 “Windows” 族;MacOSButtonMacOSCheckbox 属于 “macOS” 族。
  5. **客户端 (Client)**:
    • 客户端只与抽象工厂抽象产品的接口交互。
    • 在程序启动或配置阶段,客户端选择一个具体工厂的实例。之后,所有的创建工作都委托给这个工厂,从而保证了所有被创建的产品都来自同一个产品族。

工厂方法 vs. 抽象工厂:

  • 关注点
    • 工厂方法:关注单个产品的创建,延迟到子类。
    • 抽象工厂:关注一族产品的创建,保证兼容性。
  • 结构
    • 工厂方法:一个抽象创建者,一个抽象产品。
    • 抽象工厂:一个抽象工厂,多个抽象产品。
  • 关系:你可以认为抽象工厂内部是由多个工厂方法组成的。

四、怎么做 (How - The Blueprint)

基本步骤(以 UI 框架为例):

  1. 定义抽象产品接口:为每个要创建的组件(Button, Checkbox)定义一个接口。
  2. 创建具体产品类:为每个操作系统(Windows, macOS)实现一套完整的具体组件。
  3. **定义抽象工厂接口 GUIFactory**:声明创建所有抽象产品的方法,如 createButton(), createCheckbox()
  4. 创建具体工厂类WindowsFactory 实现 GUIFactory,返回所有 Windows 组件;MacOSFactory 实现 GUIFactory,返回所有 macOS 组件。
  5. 客户端代码获取一个具体的工厂实例,然后通过这个工厂来创建所有需要的 UI 组件。

Java 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 1. 抽象产品接口 (AbstractProduct)
interface Button { void paint(); }
interface Checkbox { void paint(); }

// 2. 具体产品 (ConcreteProduct) - Windows 家族
class WindowsButton implements Button {
@Override
public void paint() { System.out.println("Rendering a button in Windows style."); }
}
class WindowsCheckbox implements Checkbox {
@Override
public void paint() { System.out.println("Rendering a checkbox in Windows style."); }
}

// 2. 具体产品 (ConcreteProduct) - macOS 家族
class MacOSButton implements Button {
@Override
public void paint() { System.out.println("Rendering a button in macOS style."); }
}
class MacOSCheckbox implements Checkbox {
@Override
public void paint() { System.out.println("Rendering a checkbox in macOS style."); }
}

// 3. 抽象工厂接口 (AbstractFactory)
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}

// 4. 具体工厂 (ConcreteFactory) - Windows
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() { return new WindowsButton(); }
@Override
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}

// 4. 具体工厂 (ConcreteFactory) - macOS
class MacOSFactory implements GUIFactory {
@Override
public Button createButton() { return new MacOSButton(); }
@Override
public Checkbox createCheckbox() { return new MacOSCheckbox(); }
}

// 5. 客户端 (Client)
class Application {
private final Button button;
private final Checkbox checkbox;

// 客户端依赖于抽象工厂
public Application(GUIFactory factory) {
// 使用工厂来创建组件,不关心具体是哪个工厂
button = factory.createButton();
checkbox = factory.createCheckbox();
}

public void renderUI() {
button.paint();
checkbox.paint();
}
}

// 演示
public class AbstractFactoryDemo {
// 一个配置方法,根据环境返回合适的工厂
private static GUIFactory configureFactory() {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
return new WindowsFactory();
} else {
return new MacOSFactory(); // 默认为macOS
}
}

public static void main(String[] args) {
// 在程序初始化时决定使用哪个工厂
GUIFactory factory = configureFactory();

// 创建应用,注入工厂
Application app = new Application(factory);

// 渲染UI,所有组件风格保证一致
app.renderUI();
}
}

五、UML 类图

下面是上述例子的 PlantUML 代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@startuml
skinparam classAttributeIconSize 0
hide empty members

' Abstract Factory
interface GUIFactory {
+ Button createButton()
+ Checkbox createCheckbox()
}

' Concrete Factories
class WindowsFactory implements GUIFactory
class MacOSFactory implements GUIFactory

' Abstract Products
interface Button
interface Checkbox

' Concrete Products - Family 1 (Windows)
class WindowsButton implements Button
class WindowsCheckbox implements Checkbox

' Concrete Products - Family 2 (macOS)
class MacOSButton implements Button
class MacOSCheckbox implements Checkbox

' Client
class Application {
- Button button
- Checkbox checkbox
+ Application(GUIFactory factory)
+ void renderUI()
}

' Relationships
' Client depends on Abstract Factory and Abstract Products
Application -right-> GUIFactory : uses
Application ..> Button : uses
Application ..> Checkbox : uses

' Concrete Factories create Concrete Products
WindowsFactory ..> WindowsButton : creates
WindowsFactory ..> WindowsCheckbox : creates
MacOSFactory ..> MacOSButton : creates
MacOSFactory ..> MacOSCheckbox : creates

@enduml

图示解读:

  • 左侧是工厂的层次结构,右侧是平行的产品层次结构。
  • 每条虚线从一个具体工厂出发,连接到它所创建的一系列具体产品,形成一个产品族
  • 客户端 Application 位于顶层,只依赖于抽象的 GUIFactory, Button, Checkbox

六、优缺点分析

优点 (Pros):

  1. 保证产品兼容性:这是该模式的核心优点。客户端使用一个工厂,就能得到一整套相互匹配的产品,从根本上杜绝了产品混用的问题。
  2. 高度解耦:客户端代码与具体产品的实现完全分离,只依赖于抽象接口。更换整个产品族变得非常容易,只需要在初始化时更换一个不同的具体工厂实例即可。
  3. 符合开闭原则:添加一个新的产品族(比如为 Linux GTK 添加一套组件)非常方便,只需新增一个具体工厂和一套具体产品类,无需修改现有代码。

缺点 (Cons):

  1. 难以扩展新种类的产品:这是该模式的主要缺点。如果想在整个产品体系中增加一种新的产品(比如,除了 Button 和 Checkbox,我们还想增加一个 ScrollBar),那么就必须修改抽象工厂 GUIFactory 的接口,增加一个 createScrollBar() 方法。这将导致所有已有的具体工厂子类都需要进行修改,违反了开闭原则。
    • 结论:抽象工厂模式适用于产品族相对稳定,但产品族的实现(主题)经常变化的场景。

七、在 Java 中的应用

抽象工厂模式是大型系统和框架设计的基石,尤其在需要支持多种“方言”或“实现”的场景中。

  • **JDBC (Java Database Connectivity)**:这是最经典的例子。
    • java.sql.Connection 可以看作一个抽象工厂。
    • 它提供了 createStatement(), prepareStatement() 等方法来创建 Statement, PreparedStatement 对象(抽象产品)。
    • 当你从 DriverManagerDataSource 获取一个特定数据库(如 MySQL)的 Connection 实现时(MysqlConnection,一个具体工厂),这个 Connection 创建出的所有 Statement 对象也都是 MySQL 特定的实现(MysqlStatement),它们能正确地协同工作。
  • **JAXP (Java API for XML Processing)**:DocumentBuilderFactorySAXParserFactory 也是抽象工厂。你可以通过配置,让它们返回不同 XML 解析器厂商(如 Xerces, Crimson)提供的具体解析器实现。

今天关于创建型模式的讲解就到此结束了。我们学习了四个非常重要的模式:

  • 原型模式:通过克隆高效创建对象。
  • 建造者模式:优雅地构建复杂对象。
  • 工厂方法模式:解耦单个对象的创建。
  • 抽象工厂模式:保证一族对象的兼容性。