软件设计

Swift 静态工厂:为什么用 make 代替 init

Swift 静态工厂方法通过名称表达创建意图,并控制返回类型、缓存和实例复用。本文总结选择 static func make 而不是 init 的优缺点,以及它与 GoF 模式的区别。

4 分钟阅读
Swift 静态工厂:为什么用 make 代替 init 封面图

进行 iOS 开发时,你可能曾在别人的代码中看到 init(...) 而不是 static func make(...),感到疑惑。

先说结论。

Swift 静态工厂方法可以为创建流程命名,灵活调整返回类型,还能控制对象复用;当只用 init 不够灵活时,它就很有用。

今天从实践角度逐一说明为什么用 static func make 代替 init。

本文的静态工厂只是名称上类似 GoF Factory Method Pattern;该模式由子类决定创建方式。更多 Swift 实现示例见 使用工厂函数分离创建逻辑的方法,GoF 模式的边界见 工厂方法 vs. 抽象工厂。


静态工厂方法到底是什么?

名字听起来很复杂,但概念很简单。

这是一个创建并返回对象的 static 方法。不直接调用构造器,而是用一个函数封装创建过程。

大致如下。

struct Button {
    let title: String
    let style: Style

    // init外包一层的静态工厂方法
    static func makePrimary(title: String) -> Button {
        Button(title: title, style: .primary)
    }
}

调用处会是这样:Button.makePrimary(title: "확인")。

结果与直接调用 Button(title:style:) 相同,只是从名称就能看出“创建的是哪个按钮”。

这个小差异在实际代码中影响不小。


用 make 代替 init 的 4 个理由

下面是我在实际项目中感受到的优点。

1. 用名称表达意图

构造器的名称都一样,都是 init,只能通过参数组合区分。

因此,类似的 init 一多就容易混淆。使用 make(fromJSON:)、make(withDefaults:) 这样的名称,只看调用处也能知道创建的是什么。

2. 不必每次都创建新对象

每次调用 init 都会创建新实例。

而工厂方法可以返回缓存对象或已有的单例。对于 Bool 的真值和假值这类固定值,复用效率高得多。

3. 返回类型更加灵活

我个人认为这是最强大的优点。

工厂方法可以返回声明类型的子类型或协议实现。调用方不需要知道具体类型。

protocol Shape { func area() -> Double }

enum ShapeFactory {
    // 根据条件返回不同实现
    static func make(sides: Int) -> Shape {
        sides == 4 ? Square() : Triangle()
    }
}

make(sides:) 的返回类型只有一个 Shape,但实际会返回符合当前情况的实现。

表面上是一个 Shape,内部类型会随情况变化
表面上是一个 Shape,内部类型会随情况变化

4. 可以更平滑地处理失败

虽然可以使用 init? 或 throws,但工厂方法很方便直接返回可选值,或用 Result 包装结果。创建逻辑复杂时,流程会更清晰。

只加一行 make,调用处立刻清晰多了
只加一行 make,调用处立刻清晰多了

和 init 有什么不同?一眼看懂的对比

为了避免混淆,我整理成了表格。

分类 init(构造器) static func make(静态工厂)
名称 始终固定为 init 可自由指定
新实例 每次强制创建 支持复用和缓存
返回类型 只能是自身类型 可以是子类型或协议
失败处理 init? / throws 可选值、Result 等均可灵活使用
缺点 名称无法区分情况 子类化受限,发现性较低

也坦诚说说它的缺点。

如果只有工厂方法而没有 public init,就很难通过继承扩展该类型。

构造器会直接出现在自动补全中,但查找 make 通常需要知道名称,因此发现性稍低。建议使用 make、create、from 这类约定名称。


那么,什么时候该用它?

我在实践中的判断标准如下。

  1. 只需填充存储属性 → 直接用 init
  2. 创建方式有多种,想通过名称区分 → make
  3. 需要根据条件返回不同类型 → make
  4. 想复用或缓存对象 → make
我用这张判断表决定二者选哪个
我用这张判断表决定二者选哪个

总之,当创建不再只是“填充值”,而成为一个决策过程时,工厂方法就能发挥作用。

反过来,把所有创建都封装成 make 反而会让代码冗长。关键是只在需要时使用。

一开始可能不习惯,但理解意图后,阅读代码会变得更有趣。下次遇到类似情况,不妨试试 make,相信会有帮助 🙂

推荐阅读