Builder — un modèle de création qui permet de créer des objets complexes étape par étape. Contrairement à un constructeur avec une dizaine de paramètres, Builder assemble un objet via une chaîne d'appels, chacun configurant un champ. Le modèle est particulièrement utile pour les objets avec de nombreux paramètres optionnels : configuration de client réseau, paramètres de base de données, constructeurs d'alertes et de navigation. Plus d'informations sur Refactoring Guru: Builder.
Points clés
Builder — un modèle de création GoF qui sépare la construction d'un objet complexe de sa représentation. Le même processus de construction peut créer différentes représentations. Builder est utile lorsqu'un objet a de nombreux paramètres optionnels et qu'un constructeur avec dix champs est illisible et inflexible. Le modèle résout également l'anti-modèle Telescoping Constructor, où le nombre de surcharges du constructeur croît de façon exponentielle.
Structure de Builder inclut une classe interne statique Builder avec des champs reflétant les champs de la classe principale. Chaque set-méthode retourne Builder (this) pour un enchaînement fluide. La méthode finale build() crée l'objet cible en passant les valeurs des champs à un constructeur privé. La classe principale a un constructeur privé qui accepte un Builder. Client : Object.builder().setField1(val1).setField2(val2).build().
Quand utiliser Builder — objets avec 5+ champs dont seulement 2-3 sont obligatoires. Objets de configuration (RequestConfig, DatabaseConfig). Objets avec logique de validation complexe lors de la création. Objets qui doivent être immuables après la création. Sous Android, Builder est activement utilisé dans le SDK : AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Builder en Kotlin a deux approches : Builder classique style Java (via une classe imbriquée) et Builder DSL style Kotlin (via une lambda avec récepteur). Le Builder style Java est préférable pour la compatibilité Android et lorsqu'il est utilisé avec du code Java. Le DSL builder est la manière idiomatique de Kotlin : une fonction accepte une lambda à l'intérieur de laquelle this est le contexte Builder où les champs peuvent être assignés directement.
// Builder classique
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)
}
}
}
// Utilisation
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — une alternative sans classe imbriquée. Une fonction builder accepte une lambda dans le contexte d'un objet constructeur. C'est idiomatique pour Kotlin et ne nécessite pas de write-champs. Les DSL builders sont activement utilisés dans Ktor Client, kotlinx.serialization, Compose (Modifier). Le DSL builder est incompatible avec Java et ne convient pas aux bibliothèques avec une API Java.
Builder en Swift — Swift n'a pas de modèle Builder intégré, mais une interface fluide est facilement implémentée via des méthodes retournant Self. Chaque méthode configure une propriété et retourne self. Contrairement à Kotlin, Swift ne nécessite pas de classe Builder séparée — vous pouvez retourner l'objet lui-même s'il est mutable lors de l'assemblage. Pour les objets immuables, une classe Builder imbriquée est utilisée comme pour 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 a introduit @resultBuilder — un mécanisme de langage pour la construction déclarative de structures. SwiftUI, AttributedString, SceneBuilder utilisent des result builders. C'est une alternative au Builder classique : au lieu d'une chaîne de set-méthodes, result builder utilise un bloc de code avec des éléments que le compilateur assemble en un tableau ou un arbre. @ViewBuilder dans SwiftUI est l'exemple le plus connu : à l'intérieur de body, on peut écrire if, switch, ForEach, et le compilateur construit une View à partir des conditions.
Telescoping Constructor — un anti-modèle où une classe a de nombreux constructeurs surchargés avec différents ensembles de paramètres. Par exemple, trois constructeurs : HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). Avec la croissance des paramètres, le nombre de constructeurs croît de façon exponentielle — pour n champs optionnels, il faut n! combinaisons. Builder résout ce problème en permettant de spécifier uniquement les champs nécessaires.
| Caractéristique | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Quantité de code | Croissance exponentielle | Croissance linéaire | Minimale |
| Lisibilité | Faible (quel paramètre est quoi ?) | Élevée (méthode + nom) | Élevée (nom = valeur) |
| Immutabilité | Immuable | Immuable | Immuable |
| Compatibilité Java | Complete | Complete | Aucune (Kotlin seulement) |
| Validation | Dans chaque constructeur | Dans build() — une fois | Dans init() |
Kotlin named arguments + valeurs par défaut — une alternative élégante à Builder dans les projets purement Kotlin. Les paramètres du constructeur ont des valeurs par défaut, le client ne passe que ceux nécessaires : HttpConfig(baseUrl = url, timeout = 15_000). L'inconvénient est l'impossibilité de valider les champs obligatoires à la compilation. Builder fournit les champs obligatoires via le constructeur Builder (baseUrl est obligatoire). Pour les bibliothèques Java, Builder reste le standard de facto.
Builder dans Android SDK — l'un des modèles les plus courants dans la bibliothèque 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().
Pourquoi Google utilise Builder — la rétrocompatibilité. Ajouter une nouvelle méthode à Builder ne casse pas le code existant. Si Google utilisait un constructeur avec 20 paramètres, chaque nouveau champ nécessiterait une nouvelle surcharge. Builder permet d'ajouter des set-méthodes pendant des années sans breaking changes. Par exemple, NotificationCompat.Builder a ajouté setBubbleMetadata() dans Android 11 sans affecter le code existant.
Builder dans les bibliothèques Kotlin — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()), Navigation (NavOptionsBuilder). Dans les projets Kotlin, Builder est souvent combiné avec DSL : Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). Le modèle reste pertinent pour les API publiques où la rétrocompatibilité et l'interopérabilité Java sont importantes.
Questions fréquentes
Builder est excessif pour les objets avec 1-3 champs — un constructeur normal ou une data class est plus clair. Il est également excessif dans les projets Kotlin sans interopérabilité Java, où les named arguments + valeurs par défaut résolvent la même tâche plus simplement. Builder est justifié pour 5+ champs, une validation complexe ou des API Java où les named arguments ne sont pas disponibles.
Builder crée un objet complexe étape par étape (configuration des champs), Factory crée un objet entier par type ou paramètres. Builder répond à la question « comment assembler ? », Factory répond à « quoi créer ? ». Builder est souvent combiné avec Factory : Factory sélectionne le type, Builder configure les champs.
Dans SwiftUI, le rôle de Builder est joué par les result builders (@ViewBuilder, @SceneBuilder) et les modificateurs de View (.font(), .padding()). Le Builder classique n'est pas nécessaire car SwiftUI utilise une approche déclarative et des modificateurs fluides. Pour les composants UIKit, Builder est utile : UIAlertController, URLRequest, NSAttributedString.
Builder ne nécessite généralement pas de sécurité des threads car il est utilisé dans un seul thread pour assembler un objet. Si Builder est utilisé dans un environnement multithread (cas rare), synchronisez chaque set-méthode et build(). Alternative — Immutable Builder : chaque set-méthode retourne une nouvelle instance de Builder avec le champ modifié.
Retrofit.Builder est une API publique de bibliothèque qui doit fonctionner sans conteneur DI. Builder offre une flexibilité de configuration (baseUrl, convertisseurs, intercepteurs, adaptateurs d'appels personnalisés) sans dépendances vis-à-vis de Dagger ou d'autres frameworks DI. Dans une application, DI peut créer Retrofit une fois via Builder, mais Builder lui-même reste partie intégrante de l'API publique de Retrofit.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi