Builder — ett skapandemönster som gör det möjligt att skapa komplexa objekt steg för steg. Till skillnad från en konstruktor med tio parametrar, bygger Builder ihop objektet genom en kedja av anrop, var och en sätter ett fält. Mönstret är särskilt användbart för objekt med många valfria parametrar: konfiguration av nätverksklient, databasinställningar, byggare för alertar och navigering. Mer på Refactoring Guru: Builder.
Huvudpunkter
Builder (byggare) — ett GoF-skapandemönster som separerar konstruktionen av ett komplext objekt från dess representation. Samma byggprocess kan skapa olika representationer. Builder är användbart när ett objekt har många valfria parametrar och en konstruktor med tio fält är oläslig och inflexibel. Mönstret löser också problemet med Telescoping Constructor — ett anti-mönster där antalet överlagringar av konstruktorn växer exponentiellt.
Builder-struktur inkluderar en inre statisk Builder-klass med fält som kopierar huvudklassens fält. Varje set-metod returnerar Builder (this) för fluent chaining. Den slutliga build()-metoden skapar målobjektet genom att skicka fältvärdena till den privata konstruktorn. Huvudklassen har en privat konstruktor som accepterar Builder. Klient: Object.builder().setField1(val1).setField2(val2).build().
När ska Builder användas — objekt med 5+ fält, varav endast 2-3 är obligatoriska. Konfigurationsobjekt (RequestConfig, DatabaseConfig). Objekt med komplex valideringslogik vid skapande. Objekt som ska vara oföränderliga (immutable) efter skapande. I Android används Builder aktivt i SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Kotlin Builder har två tillvägagångssätt: klassisk Java-style Builder (genom en nästlad klass) och Kotlin-style DSL builder (genom en lambda med receiver). Java-style Builder föredras för Android-kompatibilitet och vid användning med Java-kod. DSL builder — idiomatiskt Kotlin-sätt: funktionen accepterar en lambda, inom vilken this är Builder-kontexten, där fält kan tilldelas direkt.
// Klassisk 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)
}
}
}
// Användning
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — alternativ utan nästlad klass. Builder-funktionen accepterar en lambda i kontexten av byggarobjektet. Detta är idiomatiskt för Kotlin och kräver inga write-fält. DSL builders används aktivt i Ktor Client, kotlinx.serialization, Compose (Modifier). DSL builder är inkompatibel med Java och passar inte för bibliotek med Java API.
Swift Builder — Swift har inget inbyggt Builder-mönster, men fluent interface kan enkelt implementeras genom metoder som returnerar Self. Varje metod sätter en egenskap och returnerar self. Till skillnad från Kotlin kräver Swift ingen separat Builder-klass — objektet självt kan returneras om det är mutable i assemblieskedet. För immutable objekt används en nästlad Builder-klass analogt med 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 introducerade @resultBuilder — en språkmekanism för deklarativ uppbyggnad av strukturer. SwiftUI, AttributedString, SceneBuilder använder result builders. Detta är ett alternativ till klassisk Builder: istället för en kedja av set-metoder använder result builder ett kodblock med element som kompilatorn samlar i en array eller ett träd. @ViewBuilder i SwiftUI — det mest kända exemplet: i body kan man skriva if, switch, ForEach, och kompilatorn bygger View från villkoren.
Telescoping Constructor — ett anti-mönster där en klass har flera överlagrade konstruktorer med olika parameteruppsättningar. Till exempel tre konstruktorer: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). Med ökningen av parametrar växer antalet konstruktorer exponentiellt — för n valfria fält behövs n! kombinationer. Builder löser detta problem genom att tillåta inställning av endast nödvändiga fält.
| Egenskap | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Kodmängd | Exponentiell tillväxt | Linjär tillväxt | Minimal |
| Läsbarhet | Låg (vilken parameter?) | Hög (metod + namn) | Hög (namn = värde) |
| Oföränderlighet | Immutable | Immutable | Immutable |
| Java-kompatibilitet | Fullständig | Fullständig | Nej (endast Kotlin) |
| Validering | I varje konstruktor | I build() — en gång | I init() |
Kotlin named arguments + default values — ett elegant alternativ till Builder i rena Kotlin-projekt. Konstruktorparametrar har standardvärden, klienten skickar endast de nödvändiga: HttpConfig(baseUrl = url, timeout = 15_000). Nackdel — omöjlighet att validera obligatoriska fält vid kompilering. Builder ger obligatoriska fält genom Builder-konstruktorn (baseUrl är obligatorisk). För Java-bibliotek förblir Builder de facto standard.
Builder i Android SDK — ett av de vanligaste mönstren i standardbiblioteket. 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().
Varför Google använder Builder — bakåtkompatibilitet. Att lägga till en ny metod i Builder bryter inte befintlig kod. Om Google hade använt en konstruktor med 20 parametrar skulle varje nytt fält kräva en ny överlagring. Builder gör det möjligt att lägga till set-metoder i åratal utan breaking changes. Till exempel lade NotificationCompat.Builder till setBubbleMetadata() i Android 11 utan att påverka befintlig kod.
Builder i Kotlin-bibliotek — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder()), Navigation (NavOptionsBuilder). I Kotlin-projekt kombineras Builder ofta med DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). Mönstret förblir relevant för offentliga API:er där bakåtkompatibilitet och Java-interoperabilitet är viktiga.
Vanliga frågor
Builder är överflödig för objekt med 1-3 fält — en vanlig konstruktor eller data class är mer begriplig. Även överflödig i Kotlin-projekt utan Java-interop, där named arguments + default values löser samma uppgift enklare. Builder är motiverad för 5+ fält, komplex validering eller Java API där named arguments inte är tillgängliga.
Builder skapar ett komplext objekt steg för steg (inställning av fält), Factory skapar ett objekt i sin helhet efter typ eller parametrar. Builder svarar på frågan «hur monterar man?», Factory — «vad ska man skapa?». Builder kombineras ofta med Factory: Factory väljer typ, Builder ställer in fälten.
I SwiftUI utförs Builder-rollen av result builders (@ViewBuilder, @SceneBuilder) och View-modifierare (.font(), .padding()). Klassisk Builder behövs inte eftersom SwiftUI använder ett deklarativt tillvägagångssätt och fluent modifiers. För UIKit-komponenter är Builder användbar: UIAlertController, URLRequest, NSAttributedString.
Builder kräver vanligtvis inte trådsäkerhet, eftersom den används i en tråd för att montera objektet. Om Builder används i en flertrådig miljö (sällsynt fall), synkronisera varje set-metod och build(). Alternativ — Immutable Builder: varje set-metod returnerar en ny Builder-instans med det ändrade fältet.
Retrofit.Builder är bibliotekets offentliga API som måste fungera utan DI-container. Builder ger konfigurationsflexibilitet (baseUrl, omvandlare, interceptorer, anpassade call-adaptere) utan beroenden av Dagger eller annan DI. Inuti applikationen kan DI skapa Retrofit en gång via Builder, men Builder själv förblir en del av Retrofit offentliga API.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också