设计模式 | 抽象工厂 (Abstract Factory)
定义
抽象工厂是一种创造性设计模式,可让您生成相关对象的家族,而无需指定具体的类。
问题
想象一下,你正在创建一个家具店的模拟器。 你的代码包括:
- 家庭相关的产品:
Chair
+Sofa
+CoffeeTable
. - 这些家具在风格上会存在一些变化,比如:IKEA (宜家),VictorianStyle (维多利亚风格),ArtDeco (艺术装饰) 等等。
您需要一种方法来创建各个家具对象,以便它们与同一系列的其他家具对象在风格上相匹配。否则, 客户在收到不匹配的家具时会感到非常沮丧。
此外,在向程序添加新产品或产品系列时,您不希望更改现有的代码。 家具供应商经常更新他们的产品目录,我们并不想每次都去更改核心代码。
方案
抽象工厂 模式建议要做的第一件事情就是浏览一下所有的不同种类的产品,并且强制统一它们的变种,以便让它们实现相同的接口。例如,所有不同种类的的 Chair 都要实现 Chair
接口;所有的 coffee table 必须实现 CoffeeTable
接口,等等。
第二步就是创建 AbstractFactory
,这是一个基础接口,它声明创建构成产品系列的所有产品的方法。(例如:createChair
, createSofa
and createCoffeeTable
)。这里重要的是保证这些方法返回先前提取的接口所表示的抽象产品类型:Chair
、 Sofa
、 CoffeeTable
。
第三步要做的事情就是去实现具体的工厂。这些工厂类要返回具体的产品类型。例如,IKEAFactory
只能返回 IKEAChair
,IKEASofa
以及 IKEACoffeeTable
对象。所有的工厂必须遵循 AbstractFactory
接口,同时创建相同的各种产品。
客户端代码只能通过抽象接口与工厂和产品协同工作。 通过这种方式,您可以通过传递不同的工厂对象来更改客户端代码中使用的产品类型。
因此,当客户端的代码要求工厂生产椅子时,它们一定不能知道具体的工厂实现类。也不能够知道椅子的具体实现类。无论它们将是 IKEA 还是 Victorian 风格的椅子,必须保证用抽象类 Chair
,并且以相同的方式来与所有的椅子一起正常运行。客户端代码将会知道这个结果,椅子实现了在接口中声明的 sit
方法。它也知道无论返回哪种类型的椅子,它将与同一工厂生产的沙发和咖啡桌的类型相匹配。
好了,那谁来创建具体的工厂对象呢?通常来讲,程序在初始化阶段创建一个具体的工厂对象,并根据配置或环境选择工厂类型。
构造
伪代码
我们用 MacOS 与 Windows 上的 Button
和 Checkbox
为示例:
产品 Button
抽象类
1 | package one.wangwei.designpatterns.abstractfactory; |
实现类 WindowsButton
:
1 | package one.wangwei.designpatterns.abstractfactory; |
实现类 MacOSButton
:
1 | package one.wangwei.designpatterns.abstractfactory; |
产品 Checkbox
抽象类
1 | package one.wangwei.designpatterns.abstractfactory; |
实现 WindowsCheckbox
类:
1 | package one.wangwei.designpatterns.abstractfactory; |
产品子类 MacOSCheckbox
:
1 | package one.wangwei.designpatterns.abstractfactory; |
定义抽象工厂 GUIFactory
:
1 | package one.wangwei.designpatterns.abstractfactory; |
实例工厂 MacOSGUIFactory
类:
1 | package one.wangwei.designpatterns.abstractfactory; |
实例工厂 WindowsGUIFactory
类:
1 | package one.wangwei.designpatterns.abstractfactory; |
客户端程序:
1 | package one.wangwei.designpatterns.abstractfactory; |
应用程序:
1 | package one.wangwei.designpatterns.abstractfactory; |
应用
问题:当业务逻辑必须与来自某个产品系列的不同产品变体一起运行时,但你不希望它依赖某个具体的产品类别(或者说如果它们事先未知)。
方案:抽象工厂能够对客户端代码隐藏它创建具体产品细节。客户端代码可以与任何工厂所生产的任何产品一起正常运行,只要保证它们使用的抽象接口。
问题:当一个类有多个 Factory Method 时,导致它自身的主要职责模糊不清
方案:在设计良好的代码里面,每一个对象必须保证单一职责原则。当一个类处理多个不同同类的产品类型时,可能需要用独立的抽象工厂替换多个工厂方法。
实现
- 绘制不同产品的矩阵与同一产品的变体。
- 为所有不同类型的产品创建抽象接口,并使所有具体产品都遵循这些接口。
- 声明抽象工厂接口。 该接口应列出所有不同类型产品的创建方法。
- 针对产品系列的每个变体实施单独的工厂类别。
- 在客户端代码中创建一个工厂初始化代码。 应根据配置或当前环境选择类型并创建一个具体的工厂。
- 在客户端代码中,将所有产品构造函数调用替换为对工厂对象中的创建方法的调用。
优缺点
Pros
- 遵循开闭原则
- 允许构建产品对象系列并保证其兼容性
- 避免了具体产品的代码与使用它们的代码相互混淆
- 将多个类对象间的职责划分开来
Cons
- 通过创建多个附加类来增加整体代码的复杂性。
JDK 中的运用
javax.xml.parsers.DocumentBuilderFactory#newInstance()
javax.xml.transform.TransformerFactory#newInstance()
javax.xml.xpath.XPathFactory#newInstance()