抽象工厂模式
抽象工厂模式
Rif设计模式笔记:抽象工厂模式 (Abstract Factory Pattern)
一、一句话概括
提供一个接口,用于创建一整套相互关联或相互依赖的对象(一个产品族),而无需指定它们具体的类。
二、为什么需要它 (Why - The Pain Point)
工厂方法模式解决了单个产品的创建问题。但如果我们需要创建的是一系列产品,并且要保证这些产品能相互兼容、协同工作,情况就变得复杂了。
想象一下,你正在开发一个跨平台的 UI 框架,需要支持 Windows 和 macOS 两种操作系统风格。
- 在 Windows 上,你需要创建
WindowsButton,WindowsCheckbox,WindowsTextField。 - 在 macOS 上,你需要创建
MacOSButton,MacOSCheckbox,MacOSTextField。
痛点:
- 产品兼容性问题:你绝对不希望在同一个界面上出现一个 Windows 风格的按钮和一个 macOS 风格的复选框。这会造成 UI 风格混乱,用户体验极差。必须保证,一旦选择了某个主题(如 Windows),所有被创建的组件都必须是该主题下的。
- 客户端逻辑混乱:如果让客户端自己去组合这些组件,代码会非常复杂且容易出错。这种代码同样违反了开闭原则,每次新增一个操作系统主题(如 Linux GTK),都需要修改所有创建组件的地方。
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 逻辑,且非常容易出错
核心问题:如何确保创建的一系列对象(一个产品族)都属于同一个“风格”或“系列”,并让客户端与具体的“风格”实现解耦?
三、它是什么 (What - The Solution)
抽象工厂模式通过引入一个 “超级工厂”(抽象工厂)来解决这个问题。这个超级工厂不生产单个产品,而是生产一整个产品族。
核心思想和角色:
- **抽象工厂 (Abstract Factory)**:
- 这是一个接口或抽象类。
- 它声明了一组返回不同抽象产品的工厂方法。例如,
createButton(),createCheckbox()。它定义了要生产哪些种类的产品。
- **具体工厂 (Concrete Factory)**:
- 实现抽象工厂接口的类。
- 一个具体工厂对应一个产品族。例如,
WindowsFactory负责生产所有 Windows 风格的组件,MacOSFactory负责生产所有 macOS 风格的组件。 - 它实现了抽象工厂中的所有方法,返回具体的、属于自己族的产品实例。
- **抽象产品 (Abstract Product)**:
- 为产品族中的每一种产品定义一个接口。例如,
Button接口,Checkbox接口。
- 为产品族中的每一种产品定义一个接口。例如,
- **具体产品 (Concrete Product)**:
- 实现抽象产品接口的具体类。
- 它们被组织成多个产品族。例如,
WindowsButton和WindowsCheckbox属于 “Windows” 族;MacOSButton和MacOSCheckbox属于 “macOS” 族。
- **客户端 (Client)**:
- 客户端只与抽象工厂和抽象产品的接口交互。
- 在程序启动或配置阶段,客户端选择一个具体工厂的实例。之后,所有的创建工作都委托给这个工厂,从而保证了所有被创建的产品都来自同一个产品族。
工厂方法 vs. 抽象工厂:
- 关注点:
- 工厂方法:关注单个产品的创建,延迟到子类。
- 抽象工厂:关注一族产品的创建,保证兼容性。
- 结构:
- 工厂方法:一个抽象创建者,一个抽象产品。
- 抽象工厂:一个抽象工厂,多个抽象产品。
- 关系:你可以认为抽象工厂内部是由多个工厂方法组成的。
四、怎么做 (How - The Blueprint)
基本步骤(以 UI 框架为例):
- 定义抽象产品接口:为每个要创建的组件(Button, Checkbox)定义一个接口。
- 创建具体产品类:为每个操作系统(Windows, macOS)实现一套完整的具体组件。
- **定义抽象工厂接口
GUIFactory**:声明创建所有抽象产品的方法,如createButton(),createCheckbox()。 - 创建具体工厂类:
WindowsFactory实现GUIFactory,返回所有 Windows 组件;MacOSFactory实现GUIFactory,返回所有 macOS 组件。 - 客户端代码获取一个具体的工厂实例,然后通过这个工厂来创建所有需要的 UI 组件。
Java 示例代码
1 | // 1. 抽象产品接口 (AbstractProduct) |
五、UML 类图
下面是上述例子的 PlantUML 代码。
1 | @startuml |
图示解读:
- 左侧是工厂的层次结构,右侧是平行的产品层次结构。
- 每条虚线从一个具体工厂出发,连接到它所创建的一系列具体产品,形成一个产品族。
- 客户端
Application位于顶层,只依赖于抽象的GUIFactory,Button,Checkbox。
六、优缺点分析
优点 (Pros):
- 保证产品兼容性:这是该模式的核心优点。客户端使用一个工厂,就能得到一整套相互匹配的产品,从根本上杜绝了产品混用的问题。
- 高度解耦:客户端代码与具体产品的实现完全分离,只依赖于抽象接口。更换整个产品族变得非常容易,只需要在初始化时更换一个不同的具体工厂实例即可。
- 符合开闭原则:添加一个新的产品族(比如为 Linux GTK 添加一套组件)非常方便,只需新增一个具体工厂和一套具体产品类,无需修改现有代码。
缺点 (Cons):
- 难以扩展新种类的产品:这是该模式的主要缺点。如果想在整个产品体系中增加一种新的产品(比如,除了 Button 和 Checkbox,我们还想增加一个
ScrollBar),那么就必须修改抽象工厂GUIFactory的接口,增加一个createScrollBar()方法。这将导致所有已有的具体工厂子类都需要进行修改,违反了开闭原则。- 结论:抽象工厂模式适用于产品族相对稳定,但产品族的实现(主题)经常变化的场景。
七、在 Java 中的应用
抽象工厂模式是大型系统和框架设计的基石,尤其在需要支持多种“方言”或“实现”的场景中。
- **JDBC (Java Database Connectivity)**:这是最经典的例子。
java.sql.Connection可以看作一个抽象工厂。- 它提供了
createStatement(),prepareStatement()等方法来创建Statement,PreparedStatement对象(抽象产品)。 - 当你从
DriverManager或DataSource获取一个特定数据库(如 MySQL)的Connection实现时(MysqlConnection,一个具体工厂),这个Connection创建出的所有Statement对象也都是 MySQL 特定的实现(MysqlStatement),它们能正确地协同工作。
- **JAXP (Java API for XML Processing)**:
DocumentBuilderFactory和SAXParserFactory也是抽象工厂。你可以通过配置,让它们返回不同 XML 解析器厂商(如 Xerces, Crimson)提供的具体解析器实现。
今天关于创建型模式的讲解就到此结束了。我们学习了四个非常重要的模式:
- 原型模式:通过克隆高效创建对象。
- 建造者模式:优雅地构建复杂对象。
- 工厂方法模式:解耦单个对象的创建。
- 抽象工厂模式:保证一族对象的兼容性。