ソフトウェア設計

Swiftの静的ファクトリ:initではなくmakeを使う理由

Swiftの静的ファクトリメソッドは、名前で生成の意図を示し、戻り値の型、キャッシュ、インスタンスの再利用を制御できます。initではなくstatic func makeを選ぶメリットとデメリット、GoFパターンとの違いを整理します。

読了 5 分
Swiftの静的ファクトリ:initではなくmakeを使う理由のカバー画像

iOS開発をしていると、他人のコードでinit(...)ではなくstatic func make(...)のようなものを見かけて、首をかしげたことがあるかもしれません。

まず結論からお話しします。

Swiftの静的ファクトリメソッドは、生成処理に名前を付け、戻り値の型を柔軟にし、オブジェクトの再利用まで制御できます。initだけでは窮屈なときに使える選択肢です。

今回は、実務の観点からinitではなくstatic func makeを使う理由を順に説明します。

この記事の静的ファクトリは、サブクラスが生成を決めるGoFのFactory Method Patternとは名前が似ているだけです。Swiftの実装例はファクトリ関数で生成ロジックを分離する方法で、GoFパターンの境界についてはファクトリメソッドと抽象ファクトリで続けて読めます。


静的ファクトリメソッドとは?

大げさな名前ですが、概念は単純です。

オブジェクトを生成して返すstaticメソッドです。イニシャライザを直接呼ぶ代わりに、生成処理を包む関数を1つ用意します。

次のような形です。

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:)を直接呼ぶ場合と結果は同じです。ただし、「どのボタンを作るのか」が名前から分かります。

この小さな違いが、実務では大きな効果を生みます。


initではなくmakeを使う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:)の戻り値の型はShape1つですが、実際には状況に合った実装が返されます。

見た目は1つのShapeでも、中身は状況に応じて異なる型
見た目は1つのShapeでも、中身は状況に応じて異なる型

4. 失敗を穏やかに処理できる

init?やthrowsでも実現できますが、ファクトリメソッドなら戻り値自体をオプショナルにしたり、Resultで包んだりしやすくなります。生成ロジックが複雑なときも流れがきれいです。

makeを1行追加しただけで、呼び出し側がとても読みやすくなりました
makeを1行追加しただけで、呼び出し側がとても読みやすくなりました

initとは何が違う?ひと目で比較

混乱しやすいので、表にまとめました。

項目 init(イニシャライザ) static func make(静的ファクトリ)
名前 常にinitで固定 自由に指定可能
新しいインスタンス 毎回強制生成 再利用・キャッシュ可能
戻り値の型 自身の型のみ サブタイプ・プロトコルが可能
失敗処理 init? / throws オプショナル・Resultなど自由
デメリット 名前で区別できない サブクラス化の制約、見つけにくい

デメリットも正直に確認しておきます。

ファクトリメソッドしかなくpublic initがない型は、継承による拡張が難しくなります。

イニシャライザはコード補完にすぐ表示されますが、makeは名前を知っているほうが見つけやすく、発見性が少し下がります。そのため、make、create、fromのような慣用的な名前がおすすめです。


では、いつ使えばよいのでしょうか?

実務で使い分けている基準は次のとおりです。

  1. 保存プロパティを埋めるだけなら → init
  2. 生成方法が複数あり、名前で区別したいなら → make
  3. 条件に応じて異なる型を返す必要があるなら → make
  4. オブジェクトを再利用・キャッシュしたいなら → make
私はこの基準表1つでどちらを使うか決めています
私はこの基準表1つでどちらを使うか決めています

まとめると、生成が単なる「値の設定」を超えて意思決定のプロセスになるとき、ファクトリメソッドが力を発揮します。

逆に、すべての生成をmakeで包むとコードが冗長になります。必要なときだけ選んで使うことが大切です。

最初はなじみにくく感じるかもしれませんが、意図をつかめばコードを読む楽しさが変わります。次に似た状況に出会ったら、makeを試してみてください。きっと役に立ちます🙂

あわせて読みたい