Builder — 複雑なオブジェクトを段階的に作成できる生成パターンです。十数のパラメーターを持つコンストラクターとは異なり、Builderは呼び出しのチェーンを通じてオブジェクトを組み立て、各呼び出しが1つのフィールドを設定します。このパターンは、ネットワーククライアント設定、データベース設定、アラートやナビゲーションのビルダーなど、多くのオプションパラメーターを持つオブジェクトに特に有用です。詳細はRefactoring Guru: 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には2つのアプローチがあります:クラシックなJavaスタイルのBuilder(ネストされたクラス経由)とKotlinスタイルのDSL builder(レシーバー付きラムダ経由)。JavaスタイルのBuilderはAndroid互換性とJavaコードとの併用に適しています。DSL builderはKotlinの慣用的な方法です:関数がラムダを受け入れ、その内部でthisがフィールドを直接割り当てられるBuilderコンテキストになります。
// クラシック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 — Swiftには組み込みのBuilderパターンはありませんが、Selfを返すメソッドを通じてfluent interfaceを簡単に実装できます。各メソッドがプロパティを設定し、selfを返します。Kotlinとは異なり、Swiftは別個のBuilderクラスを必要としません — アセンブリ中にオブジェクトがmutableであれば、オブジェクト自体を返せます。不変オブジェクトの場合は、Kotlinと同様にネストされたBuilderクラスが使用されます。
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を構築します。
Telescoping Constructor — クラスに異なるパラメーターセットを持つ複数のオーバーロードされたコンストラクターがあるアンチパターンです。例:3つのコンストラクター — HttpConfig(url)、HttpConfig(url, timeout)、HttpConfig(url, timeout, retries)。パラメーターが増えるにつれて、コンストラクターの数は指数関数的に増加します — n個のオプションフィールドに対してn!の組み合わせが必要です。Builderは必要なフィールドのみを指定できるようにすることでこの問題を解決します。
| 特性 | Telescoping Constructor | Builder | Kotlin 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 — 標準ライブラリで最も一般的なパターンの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は1〜3フィールドのオブジェクトには過剰です — 通常のコンストラクターやdata classの方が明確です。Java相互運用性のないKotlinプロジェクトでも過剰で、named arguments + デフォルト値が同じタスクをより簡単に解決します。Builderは5+フィールド、複雑な検証、またはnamed argumentsが利用できないJava APIに対して正当化されます。
Builderは1つの複雑なオブジェクトを段階的に作成し(フィールド設定)、Factoryはタイプまたはパラメーターによってオブジェクト全体を作成します。Builderは「どう組み立てるか?」に答え、Factoryは「何を作成するか?」に答えます。BuilderはしばしばFactoryと組み合わされます:Factoryがタイプを選択し、Builderがフィールドを設定します。
SwiftUIでは、Builderの役割はresult builders(@ViewBuilder, @SceneBuilder)とView修飾子(.font(), .padding())が果たします。SwiftUIは宣言的アプローチとfluent modifiersを使用するため、クラシックなBuilderは必要ありません。UIKitコンポーネントにはBuilderが有用です:UIAlertController, URLRequest, NSAttributedString。
Builderは通常、オブジェクトを組み立てるために単一スレッドで使用されるため、スレッドセーフティは必要ありません。Builderがマルチスレッド環境(まれなケース)で使用される場合は、各setメソッドとbuild()を同期してください。代替案 — Immutable Builder:各setメソッドが変更されたフィールドを持つ新しいBuilderインスタンスを返します。
Retrofit.BuilderはDIコンテナーなしで動作する必要がある公開ライブラリAPIです。Builderは、Daggerや他のDIフレームワークへの依存なしに、設定の柔軟性(baseUrl、コンバーター、インターセプター、カスタムコールアダプター)を提供します。アプリケーション内では、DIはBuilderを通じて一度Retrofitを作成できますが、Builder自体はRetrofitの公開APIの一部であり続けます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。