Builder — een creatiepatroon waarmee complexe objecten stap voor stap kunnen worden gemaakt. In tegenstelling tot een constructor met tien parameters, bouwt de Builder een object op via een keten van aanroepen, die elk één veld instellen. Het patroon is vooral nuttig voor objecten met veel optionele parameters: configuratie van een netwerkclient, database-instellingen, builders voor alerts en navigatie. Meer op Refactoring Guru: Builder.
Belangrijkste punten
Builder (bouwer) — een GoF-creatiepatroon dat de constructie van een complex object scheidt van zijn representatie. Hetzelfde bouwproces kan verschillende representaties creëren. Builder is nuttig wanneer een object veel optionele parameters heeft en een constructor met tien velden onleesbaar en inflexibel is. Het patroon lost ook het probleem van Telescoping Constructor op — een antipatroon waarbij het aantal overbelastingen van de constructor exponentieel groeit.
Builder-structuur omvat een innerlijke statische Builder-klasse met velden die de velden van de hoofdklasse kopiëren. Elke set-methode retourneert Builder (this) voor fluent chaining. De uiteindelijke build()-methode maakt het doelobject aan door veldwaarden door te geven aan de privéconstructor. De hoofdklasse heeft een privéconstructor die Builder accepteert. Client: Object.builder().setField1(val1).setField2(val2).build().
Wanneer Builder gebruiken — objecten met 5+ velden, waarvan slechts 2-3 verplicht. Configuratieobjecten (RequestConfig, DatabaseConfig). Objecten met complexe validatielogica bij aanmaak. Objecten die na aanmaak onveranderlijk (immutable) moeten zijn. In Android wordt Builder actief gebruikt in de SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Kotlin Builder heeft twee benaderingen: klassieke Java-style Builder (via een geneste klasse) en Kotlin-style DSL builder (via een lambda met receiver). Java-style Builder heeft de voorkeur voor Android-compatibiliteit en bij gebruik met Java-code. DSL builder — idiomatische Kotlin-manier: de functie accepteert een lambda, waarbinnen this de Builder-context is, waar velden direct kunnen worden toegewezen.
// Klassieke 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)
}
}
}
// Gebruik
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — alternatief zonder geneste klasse. De builder-functie accepteert een lambda in de context van het bouwobject. Dit is idiomatisch voor Kotlin en vereist geen write-velden. DSL builders worden actief gebruikt in Ktor Client, kotlinx.serialization, Compose (Modifier). DSL builder is incompatibel met Java en niet geschikt voor bibliotheken met een Java API.
Swift Builder — Swift heeft geen ingebouwd Builder-patroon, maar fluent interface kan eenvoudig worden geïmplementeerd via methoden die Self retourneren. Elke methode stelt een eigenschap in en retourneert self. In tegenstelling tot Kotlin vereist Swift geen aparte Builder-klasse — het object zelf kan worden geretourneerd als het mutable is in de assemblagfase. Voor immutable objecten wordt een geneste Builder-klasse gebruikt analoog aan 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 introduceerde @resultBuilder — een taalmechanisme voor declaratieve opbouw van structuren. SwiftUI, AttributedString, SceneBuilder gebruiken result builders. Dit is een alternatief voor klassieke Builder: in plaats van een keten van set-methoden gebruikt result builder een codeblok met elementen die de compiler verzamelt in een array of boom. @ViewBuilder in SwiftUI — het bekendste voorbeeld: in body kunnen if, switch, ForEach worden geschreven en de compiler bouwt View uit de voorwaarden.
Telescoping Constructor — een antipatroon waarbij een klasse meerdere overbelaste constructors heeft met verschillende parametersets. Bijvoorbeeld drie constructors: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). Met de toename van parameters groeit het aantal constructors exponentieel — voor n optionele velden zijn n! combinaties nodig. Builder lost dit probleem op door alleen de benodigde velden in te kunnen stellen.
| Kenmerk | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Hoeveelheid code | Exponentiële groei | Lineaire groei | Minimaal |
| Leesbaarheid | Laag (welke parameter?) | Hoog (methode + naam) | Hoog (naam = waarde) |
| Onveranderlijkheid | Immutable | Immutable | Immutable |
| Java-compatibiliteit | Volledig | Volledig | Nee (alleen Kotlin) |
| Validatie | In elke constructor | In build() — eenmalig | In init() |
Kotlin named arguments + default values — een elegant alternatief voor Builder in pure Kotlin-projecten. Constructorparameters hebben standaardwaarden, de client geeft alleen de benodigde door: HttpConfig(baseUrl = url, timeout = 15_000). Nadeel — onmogelijkheid om verplichte velden tijdens compilatie te valideren. Builder biedt verplichte velden via de Builder-constructor (baseUrl is verplicht). Voor Java-bibliotheken blijft Builder de facto standaard.
Builder in Android SDK — een van de meest voorkomende patronen in de standaardbibliotheek. 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().
Waarom Google Builder gebruikt — terugwaartse compatibiliteit. Het toevoegen van een nieuwe methode in Builder breekt bestaande code niet. Als Google een constructor met 20 parameters had gebruikt, zou elk nieuw veld een nieuwe overbelasting vereisen. Builder maakt het mogelijk jarenlang set-methoden toe te voegen zonder breaking changes. Bijvoorbeeld, NotificationCompat.Builder voegde setBubbleMetadata() toe in Android 11, zonder bestaande code te beïnvloeden.
Builder in Kotlin-bibliotheken — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder()), Navigation (NavOptionsBuilder). In Kotlin-projecten wordt Builder vaak gecombineerd met DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). Het patroon blijft relevant voor openbare API's waar terugwaartse compatibiliteit en Java-interoperabiliteit belangrijk zijn.
Veelgestelde vragen
Builder is overbodig voor objecten met 1-3 velden — een gewone constructor of data class is begrijpelijker. Ook overbodig in Kotlin-projecten zonder Java-interop, waar named arguments + default values dezelfde taak eenvoudiger oplossen. Builder is gerechtvaardigd voor 5+ velden, complexe validatie of Java API waar named arguments niet beschikbaar zijn.
Builder maakt één complex object stap voor stap (velden instellen), Factory maakt een object in zijn geheel op basis van type of parameters. Builder beantwoordt de vraag «hoe assembleren?», Factory — «wat maken?». Builder wordt vaak gecombineerd met Factory: Factory kiest het type, Builder stelt de velden in.
In SwiftUI wordt de rol van Builder vervuld door result builders (@ViewBuilder, @SceneBuilder) en View-modifiers (.font(), .padding()). Klassieke Builder is niet nodig omdat SwiftUI een declaratieve benadering en fluent modifiers gebruikt. Voor UIKit-componenten is Builder nuttig: UIAlertController, URLRequest, NSAttributedString.
Builder vereist meestal geen draadveiligheid, omdat het in één thread wordt gebruikt voor het assembleren van het object. Als Builder in een multi-thread omgeving wordt gebruikt (zeldzaam geval), synchroniseer dan elke set-methode en build(). Alternatief — Immutable Builder: elke set-methode retourneert een nieuwe Builder-instantie met het gewijzigde veld.
Retrofit.Builder is de openbare API van de bibliotheek die zonder DI-container moet werken. Builder biedt configuratieflexibiliteit (baseUrl, converters, interceptors, aangepaste call adapters) zonder afhankelijkheden van Dagger of andere DI. Binnen de applicatie kan DI Retrofit eenmalig via Builder aanmaken, maar Builder zelf blijft deel van de openbare API van Retrofit.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook