Builder — un modello creazionale che consente di creare oggetti complessi passo dopo passo. A differenza di un costruttore con una dozzina di parametri, Builder assembla un oggetto attraverso una catena di chiamate, ciascuna delle quali configura un campo. Il modello è particolarmente utile per oggetti con molti parametri opzionali: configurazione del client di rete, impostazioni del database, costruttori di avvisi e navigazione. Maggiori informazioni su Refactoring Guru: Builder.
Punti chiave
Builder — un modello creazionale GoF che separa la costruzione di un oggetto complesso dalla sua rappresentazione. Lo stesso processo di costruzione può creare rappresentazioni diverse. Builder è utile quando un oggetto ha molti parametri opzionali e un costruttore con dieci campi è illeggibile e inflessibile. Il modello risolve anche l'anti-modello Telescoping Constructor, dove il numero di sovraccarichi del costruttore cresce esponenzialmente.
Struttura di Builder include una classe interna statica Builder con campi che rispecchiano i campi della classe principale. Ogni set-metodo restituisce Builder (this) per un concatenamento fluido. Il metodo finale build() crea l'oggetto target passando i valori dei campi a un costruttore privato. La classe principale ha un costruttore privato che accetta un Builder. Client: Object.builder().setField1(val1).setField2(val2).build().
Quando usare Builder — oggetti con 5+ campi di cui solo 2-3 obbligatori. Oggetti di configurazione (RequestConfig, DatabaseConfig). Oggetti con logica di validazione complessa durante la creazione. Oggetti che devono essere immutabili dopo la creazione. In Android, Builder è attivamente utilizzato nell'SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Builder in Kotlin ha due approcci: Builder classico stile Java (tramite una classe annidata) e Builder DSL stile Kotlin (tramite una lambda con ricevitore). Il Builder stile Java è preferibile per la compatibilità con Android e quando utilizzato con codice Java. Il DSL builder è il modo idiomatico di Kotlin: una funzione accetta una lambda all'interno della quale this è il contesto Builder dove i campi possono essere assegnati direttamente.
// Builder classico
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)
}
}
}
// Utilizzo
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — un'alternativa senza classe annidata. Una funzione builder accetta una lambda nel contesto di un oggetto costruttore. Questo è idiomatico per Kotlin e non richiede write-campi. I DSL builders sono attivamente utilizzati in Ktor Client, kotlinx.serialization, Compose (Modifier). Il DSL builder è incompatibile con Java e non è adatto per librerie con API Java.
Builder in Swift — Swift non ha un modello Builder integrato, ma un'interfaccia fluida è facilmente implementata tramite metodi che restituiscono Self. Ogni metodo configura una proprietà e restituisce self. A differenza di Kotlin, Swift non richiede una classe Builder separata — puoi restituire l'oggetto stesso se è mutabile durante l'assemblaggio. Per oggetti immutabili, viene utilizzata una classe Builder annidata simile a 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 ha introdotto @resultBuilder — un meccanismo linguistico per la costruzione dichiarativa di strutture. SwiftUI, AttributedString, SceneBuilder utilizzano result builders. È un'alternativa al Builder classico: invece di una catena di set-metodi, result builder utilizza un blocco di codice con elementi che il compilatore assembla in un array o albero. @ViewBuilder in SwiftUI è l'esempio più famoso: all'interno di body puoi scrivere if, switch, ForEach, e il compilatore costruisce una View dalle condizioni.
Telescoping Constructor — un anti-modello dove una classe ha molti costruttori sovraccaricati con diversi insiemi di parametri. Ad esempio, tre costruttori: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). Con la crescita dei parametri, il numero di costruttori cresce esponenzialmente — per n campi opzionali servono n! combinazioni. Builder risolve questo problema permettendo di specificare solo i campi necessari.
| Caratteristica | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Quantità di codice | Crescita esponenziale | Crescita lineare | Minima |
| Leggibilità | Bassa (qual è il parametro?) | Alta (metodo + nome) | Alta (nome = valore) |
| Immutabilità | Immutable | Immutable | Immutable |
| Compatibilità Java | Completa | Completa | Nessuna (solo Kotlin) |
| Validazione | In ogni costruttore | In build() — una volta | In init() |
Kotlin named arguments + valori predefiniti — un'alternativa elegante a Builder in progetti puramente Kotlin. I parametri del costruttore hanno valori predefiniti, il cliente passa solo quelli necessari: HttpConfig(baseUrl = url, timeout = 15_000). Lo svantaggio è l'incapacità di validare i campi obbligatori in fase di compilazione. Builder fornisce campi obbligatori tramite il costruttore Builder (baseUrl è obbligatorio). Per le librerie Java, Builder rimane lo standard de facto.
Builder in Android SDK — uno dei modelli più comuni nella libreria standard. 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().
Perché Google usa Builder — retrocompatibilità. Aggiungere un nuovo metodo a Builder non rompe il codice esistente. Se Google usasse un costruttore con 20 parametri, ogni nuovo campo richiederebbe un nuovo sovraccarico. Builder permette di aggiungere set-metodi per anni senza breaking changes. Ad esempio, NotificationCompat.Builder ha aggiunto setBubbleMetadata() in Android 11 senza influenzare il codice esistente.
Builder nelle librerie Kotlin — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()), Navigation (NavOptionsBuilder). Nei progetti Kotlin, Builder è spesso combinato con DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). Il modello rimane rilevante per le API pubbliche dove retrocompatibilità e interoperabilità Java sono importanti.
Domande frequenti
Builder è eccessivo per oggetti con 1-3 campi — un costruttore normale o data class è più chiaro. È anche eccessivo in progetti Kotlin senza interoperabilità Java, dove named arguments + valori predefiniti risolvono lo stesso compito più semplicemente. Builder è giustificato per 5+ campi, validazione complessa o API Java dove named arguments non sono disponibili.
Builder crea un oggetto complesso passo dopo passo (configurazione dei campi), Factory crea un oggetto intero per tipo o parametri. Builder risponde alla domanda «come assemblare?», Factory risponde «cosa creare?». Builder è spesso combinato con Factory: Factory seleziona il tipo, Builder configura i campi.
In SwiftUI, il ruolo di Builder è svolto da result builders (@ViewBuilder, @SceneBuilder) e modificatori di View (.font(), .padding()). Il Builder classico non è necessario perché SwiftUI utilizza un approccio dichiarativo e modificatori fluidi. Per i componenti UIKit, Builder è utile: UIAlertController, URLRequest, NSAttributedString.
Builder normalmente non richiede thread safety poiché viene utilizzato in un singolo thread per assemblare un oggetto. Se Builder viene utilizzato in un ambiente multithread (caso raro), sincronizza ogni set-metodo e build(). Alternativa — Immutable Builder: ogni set-metodo restituisce una nuova istanza di Builder con il campo modificato.
Retrofit.Builder è un'API pubblica di libreria che deve funzionare senza un contenitore DI. Builder offre flessibilità di configurazione (baseUrl, convertitori, intercettori, adattatori di chiamata personalizzati) senza dipendenze da Dagger o altri framework DI. All'interno di un'applicazione, DI può creare Retrofit una volta tramite Builder, ma Builder stesso rimane parte dell'API pubblica di Retrofit.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche