Builder — モバイル開発におけるビルダーパターンの基礎

著者: IT Sectr 公開日: 2026-02-17 読了時間: 7 分

Builder — 複雑なオブジェクトを段階的に作成できる生成パターンです。十数のパラメーターを持つコンストラクターとは異なり、Builderは呼び出しのチェーンを通じてオブジェクトを組み立て、各呼び出しが1つのフィールドを設定します。このパターンは、ネットワーククライアント設定、データベース設定、アラートやナビゲーションのビルダーなど、多くのオプションパラメーターを持つオブジェクトに特に有用です。詳細はRefactoring Guru: Builderをご覧ください。

重要ポイント

  • Builder — プロセスと結果を分離した段階的なオブジェクト構築
  • Fluent interface — 便利な設定のためのset()/with()呼び出しのチェーン
  • 不変性 — Builderはsetterを必要としない完成されたオブジェクトを作成
  • 後方互換性 — Builderに新しいフィールドを追加してもクライアントが壊れない
  • Kotlin DSL vs Builder — Kotlinは代替としてtype-safe buildersを提供

Builderとは:ビルダーパターンの本質

Builder — 複雑なオブジェクトの構築をその表現から分離するGoFの生成パターンです。同じ構築プロセスで異なる表現を作成できます。Builderは、オブジェクトに多くのオプションパラメーターがあり、10個のフィールドを持つコンストラクターが読みにくく柔軟性に欠ける場合に有用です。このパターンは、コンストラクターのオーバーロード数が指数関数的に増加するTelescoping Constructorアンチパターンも解決します。

Builderの構造は、メインクラスのフィールドをミラーリングするフィールドを持つ内部静的Builderクラスを含みます。各setメソッドはfluent chainingのためBuilder(this)を返します。最終的なbuild()メソッドは、フィールド値をプライベートコンストラクターに渡してターゲットオブジェクトを作成します。メインクラスにはBuilderを受け取るプライベートコンストラクターがあります。クライアント:Object.builder().setField1(val1).setField2(val2).build().

Builderを使用するタイミング — 5+フィールドを持つオブジェクトで必須が2-3のみの場合。設定オブジェクト(RequestConfig, DatabaseConfig)。作成時に複雑な検証ロジックを持つオブジェクト。作成後に不変(immutable)でなければならないオブジェクト。Androidでは、BuilderはSDKで積極的に使用されています:AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder。

KotlinのBuilder:クラシック実装とDSL実装

KotlinのBuilderには2つのアプローチがあります:クラシックなJavaスタイルのBuilder(ネストされたクラス経由)とKotlinスタイルのDSL builder(レシーバー付きラムダ経由)。JavaスタイルのBuilderはAndroid互換性とJavaコードとの併用に適しています。DSL builderはKotlinの慣用的な方法です:関数がラムダを受け入れ、その内部でthisがフィールドを直接割り当てられるBuilderコンテキストになります。

kotlin
// クラシックBuilder
data class HttpConfig private constructor(
    val baseUrl: String,
    val timeout: Long = 30_000,
    val retries: Int = 3,
    val headers: Map<String, String> = emptyMap()
) {
    class Builder {
        private var baseUrl: String = ""
        private var timeout: Long = 30_000
        private var retries: Int = 3
        private var headers: MutableMap<String, String> = mutableMapOf()

        fun baseUrl(url: String) = apply { this.baseUrl = url }
        fun timeout(ms: Long) = apply { this.timeout = ms }
        fun retries(n: Int) = apply { this.retries = n }
        fun header(key: String, value: String) = apply { headers[key] = value }

        fun build(): HttpConfig {
            require(baseUrl.isNotBlank()) { "baseUrl is required" }
            return HttpConfig(baseUrl, timeout, retries, headers)
        }
    }
}

// 使用法
val config = HttpConfig.Builder()
    .baseUrl("https://api.example.com")
    .timeout(15_000)
    .header("Authorization", "Bearer token")
    .build()

Kotlin DSL builder — ネストされたクラスなしの代替手段。ビルダー関数はビルダーオブジェクトのコンテキストでラムダを受け入れます。これはKotlinにとって慣用的で、writeフィールドを必要としません。DSL buildersはKtor Client、kotlinx.serialization、Compose(Modifier)で積極的に使用されています。DSL builderはJavaと互換性がなく、Java APIを持つライブラリには適していません。

SwiftのBuilder:result buildersとチェーン

SwiftのBuilder — Swiftには組み込みのBuilderパターンはありませんが、Selfを返すメソッドを通じてfluent interfaceを簡単に実装できます。各メソッドがプロパティを設定し、selfを返します。Kotlinとは異なり、Swiftは別個のBuilderクラスを必要としません — アセンブリ中にオブジェクトがmutableであれば、オブジェクト自体を返せます。不変オブジェクトの場合は、Kotlinと同様にネストされたBuilderクラスが使用されます。

swift
struct NetworkRequest {
    let url: String
    let method: HTTPMethod
    let headers: [String: String]
    let body: Data?
    let timeout: TimeInterval

    final class Builder {
        private var url: String = ""
        private var method: HTTPMethod = .get
        private var headers: [String: String] = [:]
        private var body: Data? = nil
        private var timeout: TimeInterval = 30

        func withURL(_: String) -> Self { /* self */ }
        func withMethod(_: HTTPMethod) -> Self { /* self */ }
        func withHeader(key: String, value: String) -> Self { /* self */ }
        func withBody(_: Data) -> Self { /* self */ }
        func withTimeout(_: TimeInterval) -> Self { /* self */ }

        func build() throws -> NetworkRequest {
            guard !url.isEmpty else { throw BuilderError.missingURL }
            return NetworkRequest(
                url: url, method: method, headers: headers,
                body: body, timeout: timeout
            )
        }
    }
}

Result Builders — Swift 5.4で@resultBuilderが導入されました — 宣言的な構造構築のための言語メカニズムです。SwiftUI、AttributedString、SceneBuilderがresult buildersを使用しています。これはクラシックなBuilderの代替です:setメソッドのチェーンの代わりに、result builderはコンパイラが配列やツリーに組み立てる要素を含むコードブロックを使用します。SwiftUIの@ViewBuilderが最も有名な例です:body内にif、switch、ForEachを記述でき、コンパイラが条件からViewを構築します。

Builder vs Telescoping Constructor:アプローチ比較

Telescoping Constructor — クラスに異なるパラメーターセットを持つ複数のオーバーロードされたコンストラクターがあるアンチパターンです。例:3つのコンストラクター — HttpConfig(url)、HttpConfig(url, timeout)、HttpConfig(url, timeout, retries)。パラメーターが増えるにつれて、コンストラクターの数は指数関数的に増加します — n個のオプションフィールドに対してn!の組み合わせが必要です。Builderは必要なフィールドのみを指定できるようにすることでこの問題を解決します。

特性Telescoping ConstructorBuilderKotlin named args
コード量指数関数的増加線形的増加最小限
可読性低い(どのパラメーターが何か?)高い(メソッド+名前)高い(名前=値)
不変性不変不変不変
Java互換性完全完全なし(Kotlinのみ)
検証各コンストラクター内build()内 — 一度init()内

Kotlin named arguments + デフォルト値 — 純粋なKotlinプロジェクトにおけるBuilderのエレガントな代替手段です。コンストラクターパラメーターにはデフォルト値があり、クライアントは必要なものだけを渡します:HttpConfig(baseUrl = url, timeout = 15_000)。欠点はコンパイル時に必須フィールドを検証できないことです。BuilderはBuilderコンストラクターを通じて必須フィールドを提供します(baseUrlは必須)。Javaライブラリでは、Builderが事実上の標準であり続けています。

Android SDKのBuilder:AlertDialog, Retrofit, OkHttp

Android SDKのBuilder — 標準ライブラリで最も一般的なパターンの1つです。AlertDialog.Builder: new AlertDialog.Builder(context).setTitle().setMessage().setPositiveButton().create(). Retrofit.Builder: new Retrofit.Builder().baseUrl().addConverterFactory().build(). OkHttpClient.Builder: new OkHttpClient.Builder().connectTimeout().addInterceptor().build(). NotificationCompat.Builder: setContentTitle().setContentText().setSmallIcon().build().

GoogleがBuilderを使用する理由 — 後方互換性です。Builderに新しいメソッドを追加しても既存のコードは壊れません。Googleが20のパラメーターを持つコンストラクターを使用していた場合、新しいフィールドごとに新しいオーバーロードが必要になっていました。Builderは破壊的変更なしに長年にわたってsetメソッドを追加することを可能にします。例えば、NotificationCompat.BuilderはAndroid 11でsetBubbleMetadata()を追加し、既存のコードに影響を与えませんでした。

KotlinライブラリのBuilder — Ktor(HttpClientBuilder)、Coil(ImageRequest.Builder)、Room(Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build())、Navigation(NavOptionsBuilder)。Kotlinプロジェクトでは、BuilderはしばしばDSLと組み合わせられます:Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()。このパターンは、後方互換性とJava相互運用性が重要な公開APIに関連し続けています。

よくある質問

Builderが過剰なのはいつですか?

Builderは1〜3フィールドのオブジェクトには過剰です — 通常のコンストラクターやdata classの方が明確です。Java相互運用性のないKotlinプロジェクトでも過剰で、named arguments + デフォルト値が同じタスクをより簡単に解決します。Builderは5+フィールド、複雑な検証、またはnamed argumentsが利用できないJava APIに対して正当化されます。

BuilderとFactoryの違いは?

Builderは1つの複雑なオブジェクトを段階的に作成し(フィールド設定)、Factoryはタイプまたはパラメーターによってオブジェクト全体を作成します。Builderは「どう組み立てるか?」に答え、Factoryは「何を作成するか?」に答えます。BuilderはしばしばFactoryと組み合わされます:Factoryがタイプを選択し、Builderがフィールドを設定します。

SwiftUIにBuilderは必要ですか?

SwiftUIでは、Builderの役割はresult builders(@ViewBuilder, @SceneBuilder)とView修飾子(.font(), .padding())が果たします。SwiftUIは宣言的アプローチとfluent modifiersを使用するため、クラシックなBuilderは必要ありません。UIKitコンポーネントにはBuilderが有用です:UIAlertController, URLRequest, NSAttributedString。

Builderをスレッドセーフにするには?

Builderは通常、オブジェクトを組み立てるために単一スレッドで使用されるため、スレッドセーフティは必要ありません。Builderがマルチスレッド環境(まれなケース)で使用される場合は、各setメソッドとbuild()を同期してください。代替案 — Immutable Builder:各setメソッドが変更されたフィールドを持つ新しいBuilderインスタンスを返します。

なぜRetrofitはDIではなくBuilderを使用するのですか?

Retrofit.BuilderはDIコンテナーなしで動作する必要がある公開ライブラリAPIです。Builderは、Daggerや他のDIフレームワークへの依存なしに、設定の柔軟性(baseUrl、コンバーター、インターセプター、カスタムコールアダプター)を提供します。アプリケーション内では、DIはBuilderを通じて一度Retrofitを作成できますが、Builder自体はRetrofitの公開APIの一部であり続けます。

まとめ

  • Builder — fluent interfaceによる段階的なオブジェクト構築
  • Kotlin Builder — クラシック(ネストクラス)とDSL(レシーバー付きラムダ)
  • Swift Builder — 宣言的コードのためのネストクラスまたは@resultBuilder
  • 不変性 — Builderはプライベートコンストラクターを通じて不変オブジェクトを作成
  • Android SDK — AlertDialog, Retrofit, OkHttp, NotificationCompat — 業界標準
  • 後方互換性 — Builderへのフィールド追加は既存コードを壊さない
  • Kotlinの代替 — named arguments + デフォルト値は純粋なKotlinプロジェクトでよりシンプル

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください