Builder — egy létrehozási minta, amely lehetővé teszi összetett objektumok lépésenkénti létrehozását. Ellentétben a tíz paraméterrel rendelkező konstruktorral, a Builder az objektumot hívások láncán keresztül állítja össze, amelyek mindegyike egy mezőt állít be. A minta különösen hasznos sok opcionális paraméterrel rendelkező objektumokhoz: hálózati kliens konfigurációja, adatbázis beállítások, riasztások és navigáció építészei. Bővebben a Refactoring Guru: Builder oldalon.
Főbb pontok
Builder (építész) — egy GoF létrehozási minta, amely elválasztja az összetett objektum felépítését annak reprezentációjától. Ugyanaz az építési folyamat különböző reprezentációkat hozhat létre. A Builder akkor hasznos, ha egy objektumnak sok opcionális paramétere van, és a tíz mezővel rendelkező konstruktor olvashatatlan és rugalmatlan. A minta megoldja a Telescoping Constructor problémáját is — egy anti-mintát, ahol a konstruktor túlterheléseinek száma exponenciálisan nő.
Builder struktúrája magában foglal egy belső statikus Builder osztályt, amelynek mezői a fő osztály mezőit másolják. Minden set-metódus visszaadja a Builder-t (this) a fluent chaining-hez. A végső build() metódus létrehozza a cél objektumot, átadva a mezőértékeket a privát konstruktornak. A fő osztálynak privát konstruktora van, amely elfogadja a Builder-t. Ügyfél: Object.builder().setField1(val1).setField2(val2).build().
Mikor használjuk a Builder-t — 5+ mezővel rendelkező objektumok, amelyekből csak 2-3 kötelező. Konfigurációs objektumok (RequestConfig, DatabaseConfig). Összetett érvényesítési logikával rendelkező objektumok létrehozáskor. Objektumok, amelyeknek létrehozás után megváltoztathatatlannak (immutable) kell lenniük. Androidban a Builder aktívan használatos az SDK-ban: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Kotlin Builder két megközelítéssel rendelkezik: klasszikus Java-style Builder (beágyazott osztályon keresztül) és Kotlin-style DSL builder (lambda fogadóval). A Java-style Builder előnyösebb az Android kompatibilitás és a Java kóddal való használat esetén. DSL builder — idiomatikus Kotlin mód: a függvény lambdát fogad, amelyen belül a this a Builder kontextus, ahol a mezők közvetlenül hozzárendelhetők.
// Klasszikus 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)
}
}
}
// Használat
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — alternatíva beágyazott osztály nélkül. A builder-függvény lambdát fogad az építész objektum kontextusában. Ez idiomatikus Kotlinhoz, és nem igényel write-mezőket. A DSL builders aktívan használatosak a Ktor Client, kotlinx.serialization, Compose (Modifier) rendszerekben. A DSL builder nem kompatibilis Java-val, és nem alkalmas Java API-val rendelkező könyvtárakhoz.
Swift Builder — a Swift nem rendelkezik beépített Builder mintával, de a fluent interface könnyen megvalósítható Self-et visszaadó metódusokon keresztül. Minden metódus beállít egy tulajdonságot és visszaadja a self-et. Kotlinnal ellentétben a Swift nem igényel külön Builder osztályt — maga az objektum visszaadható, ha az összeszerelési szakaszban mutable. Immutable objektumokhoz beágyazott Builder osztály használatos, hasonlóan a Kotlinhoz.
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 — a Swift 5.4 bevezette a @resultBuilder-t — egy nyelvi mechanizmust struktúrák deklaratív felépítéséhez. A SwiftUI, AttributedString, SceneBuilder result builders-t használ. Ez egy alternatíva a klasszikus Builder helyett: a set-metódusok lánca helyett a result builder egy kódblokkot használ elemekkel, amelyeket a fordító egy tömbben vagy fában gyűjt össze. A @ViewBuilder a SwiftUI-ban — a leghíresebb példa: a body-n belül írható if, switch, ForEach, és a fordító View-t épít a feltételekből.
Telescoping Constructor — egy anti-minta, ahol egy osztálynak több túlterhelt konstruktora van különböző paraméterkészletekkel. Például három konstruktor: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). A paraméterek növekedésével a konstruktorok száma exponenciálisan nő — n opcionális mezőhöz n! kombináció szükséges. A Builder megoldja ezt a problémát azáltal, hogy csak a szükséges mezők beállítását teszi lehetővé.
| Jellemző | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Kód mennyisége | Exponenciális növekedés | Lineáris növekedés | Minimális |
| Olvashatóság | Alacsony (melyik paraméter?) | Magas (metódus + név) | Magas (név = érték) |
| Megváltoztathatatlanság | Immutable | Immutable | Immutable |
| Java kompatibilitás | Teljes | Teljes | Nem (csak Kotlin) |
| Érvényesítés | Minden konstruktorban | build()-ben — egyszer | init()-ben |
Kotlin named arguments + default values — elegáns alternatíva a Builder helyett tiszta Kotlin projektekben. A konstruktor paramétereinek alapértelmezett értékei vannak, az ügyfél csak a szükségeseket adja át: HttpConfig(baseUrl = url, timeout = 15_000). Hátrány — a kötelező mezők érvényesítésének lehetetlensége fordítási szakaszban. A Builder kötelező mezőket ad a Builder konstruktoron keresztül (baseUrl kötelező). Java könyvtárakhoz a Builder de facto szabvány marad.
Builder az Android SDK-ban — az egyik leggyakoribb minta a szabványos könyvtárban. 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().
Miért használ a Google Builder-t — visszafelé kompatibilitás. Egy új metódus hozzáadása a Builder-ben nem töri meg a meglévő kódot. Ha a Google 20 paraméteres konstruktort használt volna, minden új mezőhöz új túlterhelésre lett volna szükség. A Builder lehetővé teszi set-metódusok hozzáadását évekig breaking changes nélkül. Például a NotificationCompat.Builder hozzáadta a setBubbleMetadata() metódust Android 11-ben anélkül, hogy befolyásolta volna a meglévő kódot.
Builder Kotlin könyvtárakban — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder()), Navigation (NavOptionsBuilder). Kotlin projektekben a Builder gyakran kombinálódik DSL-lel: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). A minta továbbra is releváns a nyilvános API-khoz, ahol fontos a visszafelé kompatibilitás és a Java interoperabilitás.
Gyakran ismételt kérdések
A Builder felesleges az 1-3 mezővel rendelkező objektumokhoz — a szokásos konstruktor vagy data class érthetőbb. Szintén felesleges a Java interoperabilitás nélküli Kotlin projektekben, ahol a named arguments + default values egyszerűbben oldják meg ugyanazt a feladatot. A Builder 5+ mező, összetett érvényesítés vagy olyan Java API esetén indokolt, ahol a named arguments nem elérhetők.
A Builder egy összetett objektumot hoz létre lépésenként (mezők beállítása), a Factory egy objektumot teljes egészében hoz létre típus vagy paraméterek alapján. A Builder a «hogyan állítsuk össze?» kérdésre válaszol, a Factory — «mit hozzunk létre?». A Builder gyakran kombinálódik a Factory-val: a Factory kiválasztja a típust, a Builder beállítja a mezőket.
A SwiftUI-ban a Builder szerepét a result builders (@ViewBuilder, @SceneBuilder) és a View módosítók (.font(), .padding()) töltik be. Klasszikus Builder nem szükséges, mivel a SwiftUI deklaratív megközelítést és fluent modifiers-t használ. UIKit komponensekhez a Builder hasznos: UIAlertController, URLRequest, NSAttributedString.
A Builder általában nem igényel szálbiztonságot, mivel egy szálban használatos az objektum összeszereléséhez. Ha a Builder többszálas környezetben használatos (ritka eset), szinkronizáljon minden set-metódust és build()-et. Alternatíva — Immutable Builder: minden set-metódus egy új Builder példányt ad vissza a módosított mezővel.
A Retrofit.Builder a könyvtár nyilvános API-ja, amelynek DI konténer nélkül kell működnie. A Builder konfigurációs rugalmasságot biztosít (baseUrl, konverterek, interceptors, egyéni call adapterek) anélkül, hogy függne Dagger-től vagy más DI-től. Az alkalmazáson belül a DI létrehozhatja a Retrofit-ot egyszer a Builder-en keresztül, de maga a Builder a Retrofit nyilvános API-jának része marad.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is