使用 Swift 开发应用时,你每天往往要写几十次对象创建代码。
但到了某个阶段,只使用 init 会越来越受限。
很多人一开始什么都用 init,随着项目变大,便会开始了解工厂方法模式。
本文结合实际代码,讲解 Swift 工厂方法模式是什么,以及为什么用工厂代替 init。
一句话说明工厂方法模式
先直接说结论。
工厂方法会把对象创建逻辑移到 init 外部的独立方法中,为它命名,并自由选择返回类型。
在 Swift 中,通常指通过 static func 创建的静态工厂方法。
本文讨论的是通过类型方法封装创建过程的静态工厂。它不同于由子类决定创建类型的 GoF 模式;两者的边界已在工厂方法 vs. 抽象工厂中进行比较。
init 只能在语言规定的规则内运行。
它不能自定义名称,而且必须始终创建自身类型的新实例。
工厂方法解除了这些限制。
来看一个简单的例子。
struct Color {
let r, g, b: Double
// 通过名称表达创建意图
static func rgb(_ r: Double, _ g: Double, _ b: Double) -> Color {
Color(r: r, g: g, b: b)
}
static func gray(_ v: Double) -> Color {
Color(r: v, g: v, b: v)
}
}
像Color.gray(0.5)这样调用时,只看代码也能知道它会创建灰色。
为什么用工厂代替 init?
根据我的实际经验,主要有四个原因。
第一,可以为它命名。
所有 init 的名称都相同,只能通过参数来区分。
如果有多个接收两个Double的构造器,很容易让人困惑。
工厂方法可以通过from(hex:)、rgb(_:_:_:)这样的名称表达意图。
第二,不必每次都创建新实例。
init 总会返回新对象,而工厂可以返回缓存值或单例。
第三,可以灵活选择返回类型。
根据情况,它可以返回子类型或其他实现。
第四,可以更清晰地表达失败处理。
下面是一个使用缓存的工厂示例。
final class IconCache {
private static var store: [String: Icon] = [:]
// 名称相同时复用已创建的图标
static func icon(named name: String) -> Icon {
if let cached = store[name] { return cached }
let icon = Icon(name: name)
store[name] = icon
return icon
}
}
多次调用同一个图标时,可以节省内存。
那么,现在可以不用 init 了吗?
不,绝对不是。
工厂并不是取代 init,而是封装它的工具。
即使在工厂方法中,最终也要调用 init 来创建对象。
如果只是简单的值类型,或者创建逻辑很明确,直接使用 init 更好。
无谓地用工厂封装,只会让代码变长、变难读。
工厂真正发挥作用的场景另有其处。
- 创建方式有多种,需要通过名称区分时
- 需要通过缓存、对象池等方式复用时
- 想把复杂的初始化流程隐藏在一个地方时
- 想返回协议类型来隐藏实现时
如果不符合这些条件,使用 init 才是正确选择。
Swift 标准库也会这样区分使用。
Array(repeating:count:)是 init,但像UIColor.systemBlue这样的静态属性,实际上也是工厂的近亲。
实际工作中的判断标准总结
我把自己感到困惑时使用的标准整理成了表格。
| 情况 | 推荐 |
|---|---|
| 存储简单值 | init |
| 创建方式有多种 | 工厂方法 |
| 实例复用・缓存 | 工厂方法 |
| 隐藏实现并返回协议 | 工厂方法 |
| 初始化只需一行 | init |
总之,init 处理“如何创建”,而工厂处理“创建什么以及为什么创建”。
两者不是竞争关系,而是职责不同的搭档。
建议先从 init 开始,等创建逻辑变复杂后再迁移到工厂。
记住今天介绍的标准,代码就会整洁许多。祝你 Swift 编程愉快!

