学习设计模式时,总有一个让人至少卡住一次的阶段。
那就是工厂方法 vs 抽象工厂。两者名称相似,而且都能“代替我们创建对象”,确实很容易混淆。
这也是面试中的常见问题,所以我在本文中一次整理好了。
先说重点。工厂方法把对象要创建成哪个类交给子类决定;抽象工厂则一次性创建彼此相关的多个对象,也就是产品族。
像 static func make 这样为创建过程命名的静态工厂,与 GoF 工厂方法并不是一回事。如果想了解实际的 API 设计,建议先把它与 Swift 静态工厂方法 区分开。
一个,还是一整套?这就是最大的区别。
什么是工厂方法?
工厂方法是一种将对象创建责任从父类转交给子类的模式。
父类只规定“创建按钮”,具体创建哪种按钮由子类决定。
结合代码会更快理解。
protocol Button { func render() }
class Dialog {
// 工厂方法:由子类决定创建什么
func createButton() -> Button { fatalError("在子类中实现") }
func render() {
let button = createButton()
button.render()
}
}
class IOSDialog: Dialog {
override func createButton() -> Button { IOSButton() }
}
Dialog 不需要知道自己使用哪种按钮。出现新平台时,只需再添加一个子类即可。
核心是继承。通过子类化来替换创建位置。
抽象工厂有什么不同?
抽象工厂是一种将相关对象绑定成一组来创建的模式。
它负责的不是一个按钮,而是按钮、复选框、滚动条等必须一起使用的对象。
protocol GUIFactory {
func createButton() -> Button
func createCheckbox() -> Checkbox
}
class IOSFactory: GUIFactory {
func createButton() -> Button { IOSButton() }
func createCheckbox() -> Checkbox { IOSCheckbox() }
}
class MacFactory: GUIFactory {
func createButton() -> Button { MacButton() }
func createCheckbox() -> Checkbox { MacCheckbox() }
}
关键在于一致性。使用 iOS 工厂时,按钮和复选框都会采用 iOS 风格,不会出现 iOS 按钮混入 Mac 复选框的问题。
抽象工厂通常从外部注入工厂对象后使用。它依靠组合,而不是继承来工作。
工厂方法通过继承解决“创建什么”,
抽象工厂则通过组合解决“创建哪一套”。
差异一览
只用语言解释容易混淆,所以整理成了表格。
| 分类 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 目的 | 委托创建单个对象 | 创建相关对象族 |
| 基础 | 继承(子类化) | 组合(组装) |
| 创建内容 | 一种产品 | 多个产品(产品族) |
| 扩展方式 | 添加新子类 | 添加新工厂 |
| 常见示例 | createButton() | 完整 GUIFactory |
有意思的是,抽象工厂的内部实际上经常由工厂方法组成。与其说两者竞争,不如看作适用规模不同的模式。
何时使用,何时避免
实际工作中的判断标准比想象中简单。
- 如果只需创建一种对象,工厂方法就够了。专门再创建工厂接口会属于过度设计。
- 如果有两种以上的对象必须一起运行,可以考虑抽象工厂。
- 如果产品族以后不会增加,很多时候直接使用条件分支更好。
| 情况 | 判断 |
|---|---|
| 只创建一个产品 | 工厂方法 |
| 产品作为一套一起运行 | 抽象工厂 |
| 产品族经常增加 | 抽象工厂 |
| 只需 2~3 个分支 | 不使用模式,采用简单分支 |
模式是工具,而不是目的。对不会扩展的代码提前加入抽象,只会让代码更难阅读。
面试时可以这样问
Q. 请说明工厂方法与抽象工厂的区别。 A. 工厂方法是基于继承、将单个对象的创建委托给子类的模式。抽象工厂则基于组合,以一致的方式创建相关对象族。核心区别在于创建一个对象还是一整套对象。
Q. 抽象工厂内部会使用工厂方法吗? A. 会,而且很常见。抽象工厂的每个创建方法,通常都会在内部以工厂方法的形式实现。两者不是对立概念,而是处理范围不同的模式。
现在只要看到这两个模式的名称,就能想到“一个还是一套”,说明你已经掌握重点了。面试前再快速读一遍本文,就会更有把握。

