Builder — vytvářecí vzor, který umožňuje vytvářet složité objekty krok za krokem. Na rozdíl od konstruktoru s desítkou parametrů Builder skládá objekt prostřednictvím řetězce volání, z nichž každé nastavuje jedno pole. Vzor je zvláště užitečný pro objekty s mnoha volitelnými parametry: konfigurace síťového klienta, nastavení databáze, stavitelé alertů a navigace. Více na Refactoring Guru: Builder.
Hlavní body
Builder (stavitel) — vytvářecí vzor GoF, který odděluje vytváření složitého objektu od jeho reprezentace. Stejný proces vytváření může vytvářet různé reprezentace. Builder je užitečný, když má objekt mnoho volitelných parametrů a konstruktor s deseti poli je nečitelný a nepružný. Vzor také řeší problém Telescoping Constructor — anti-vzoru, kde počet přetížení konstruktoru roste exponenciálně.
Struktura Builder zahrnuje vnitřní statickou třídu Builder s poli, která kopírují pole hlavní třídy. Každá set-metoda vrací Builder (this) pro fluent chaining. Finální build() metoda vytváří cílový objekt předáním hodnot polí soukromému konstruktoru. Hlavní třída má soukromý konstruktor přijímající Builder. Klient: Object.builder().setField1(val1).setField2(val2).build().
Kdy použít Builder — objekty s 5+ poli, z nichž pouze 2-3 jsou povinná. Konfigurační objekty (RequestConfig, DatabaseConfig). Objekty se složitou logikou validace při vytváření. Objekty, které by měly být po vytvoření neměnné (immutable). V Androidu je Builder aktivně používán v SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Kotlin Builder má dva přístupy: klasický Java-style Builder (prostřednictvím vnořené třídy) a Kotlin-style DSL builder (prostřednictvím lambdy s přijímačem). Java-style Builder je preferován pro kompatibilitu s Androidem a při použití s Java kódem. DSL builder — idiomatický způsob Kotlin: funkce přijímá lambdu, uvnitř které je this kontext Builder, kde lze pole přímo přiřazovat.
// Klasický 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)
}
}
}
// Použití
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — alternativa bez vnořené třídy. Builder-funkce přijímá lambdu v kontextu objektu-stavitele. To je idiomatické pro Kotlin a nevyžaduje write-pole. DSL builders jsou aktivně používány v Ktor Client, kotlinx.serialization, Compose (Modifier). DSL builder není kompatibilní s Java a není vhodný pro knihovny s Java API.
Swift Builder — Swift nemá vestavěný vzor Builder, ale fluent interface lze snadno implementovat pomocí metod vracejících Self. Každá metoda nastavuje vlastnost a vrací self. Na rozdíl od Kotlin, Swift nevyžaduje samostatnou třídu Builder — lze vrátit samotný objekt, pokud je ve fázi sestavování mutable. Pro immutable objekty se používá vnořená třída Builder analogicky s Kotlin.
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 zavedl @resultBuilder — jazykový mechanismus pro deklarativní vytváření struktur. SwiftUI, AttributedString, SceneBuilder používají result builders. To je alternativa ke klasickému Builderu: místo řetězce set-metod result builder používá blok kódu s prvky, které kompilátor shromažďuje do pole nebo stromu. @ViewBuilder ve SwiftUI — nejznámější příklad: uvnitř body lze psát if, switch, ForEach a kompilátor vytváří View z podmínek.
Telescoping Constructor — anti-vzor, kde třída má několik přetížených konstruktorů s různými sadami parametrů. Například tři konstruktory: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). S růstem parametrů počet konstruktorů roste exponenciálně — pro n volitelných polí je potřeba n! kombinací. Builder řeší tento problém tím, že umožňuje nastavit pouze potřebná pole.
| Vlastnost | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Množství kódu | Exponenciální růst | Lineární růst | Minimální |
| Čitelnost | Nízká (který parametr?) | Vysoká (metoda + název) | Vysoká (název = hodnota) |
| Neměnnost | Immutable | Immutable | Immutable |
| Java kompatibilita | Plná | Plná | Ne (pouze Kotlin) |
| Validace | V každém konstruktoru | V build() — jednou | V init() |
Kotlin named arguments + default values — elegantní alternativa k Builderu v čistých Kotlin projektech. Parametry konstruktoru mají výchozí hodnoty, klient předává pouze potřebné: HttpConfig(baseUrl = url, timeout = 15_000). Nevýhoda — nemožnost validace povinných polí ve fázi kompilace. Builder poskytuje povinná pole prostřednictvím konstruktoru Builder (baseUrl je povinný). Pro Java knihovny zůstává Builder de facto standardem.
Builder v Android SDK — jeden z nejrozšířenějších vzorů ve standardní knihovně. 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().
Proč Google používá Builder — zpětná kompatibilita. Přidání nové metody do Builderu nenarušuje existující kód. Pokud by Google použil konstruktor s 20 parametry, každé nové pole by vyžadovalo nové přetížení. Builder umožňuje přidávat set-metody po léta bez breaking changes. Například NotificationCompat.Builder přidal setBubbleMetadata() v Android 11, aniž by ovlivnil existující kód.
Builder v Kotlin knihovnách — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder()), Navigation (NavOptionsBuilder). V Kotlin projektech se Builder často kombinuje s DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). Vzor zůstává relevantní pro veřejná API, kde je důležitá zpětná kompatibilita a Java interoperabilita.
Často kladené otázky
Builder je nadbytečný pro objekty s 1-3 poli — běžný konstruktor nebo data class jsou srozumitelnější. Také je nadbytečný v Kotlin projektech bez Java interoperability, kde named arguments + default values řeší stejný úkol jednodušeji. Builder je opodstatněný pro 5+ polí, složitou validaci nebo Java API, kde named arguments nejsou dostupné.
Builder vytváří jeden složitý objekt krok za krokem (nastavení polí), Factory vytváří objekt celý podle typu nebo parametrů. Builder odpovídá na otázku «jak sestavit?», Factory — «co vytvořit?». Builder se často kombinuje s Factory: Factory vybírá typ, Builder nastavuje pole.
Ve SwiftUI roli Builderu plní result builders (@ViewBuilder, @SceneBuilder) a modifikátory View (.font(), .padding()). Klasický Builder není potřeba, protože SwiftUI používá deklarativní přístup a fluent modifiers. Pro UIKit komponenty je Builder užitečný: UIAlertController, URLRequest, NSAttributedString.
Builder obvykle nevyžaduje bezpečnost vláken, protože se používá v jednom vlákně pro sestavení objektu. Pokud je Builder používán ve vícevláknovém prostředí (vzácný případ), synchronizujte každou set-metodu a build(). Alternativa — Immutable Builder: každá set-metoda vrací novou instanci Builderu se změněným polem.
Retrofit.Builder je veřejné API knihovny, které musí fungovat bez DI kontejneru. Builder poskytuje flexibilitu konfigurace (baseUrl, konvertory, interceptory, vlastní call adaptéry) bez závislostí na Dagger nebo jiném DI. Uvnitř aplikace může DI vytvořit Retrofit jednou přes Builder, ale samotný Builder zůstává součástí veřejného API Retrofit.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také