Builder — un patrón de creación que permite crear objetos complejos paso a paso. A diferencia de un constructor con una docena de parámetros, Builder ensambla un objeto mediante una cadena de llamadas, cada una configurando un campo. El patrón es especialmente útil para objetos con muchos parámetros opcionales: configuración de cliente de red, ajustes de base de datos, constructores de alertas y navegación. Más información en Refactoring Guru: Builder.
Puntos clave
Builder — un patrón creacional GoF que separa la construcción de un objeto complejo de su representación. Un mismo proceso de construcción puede crear diferentes representaciones. Builder es útil cuando un objeto tiene muchos parámetros opcionales y un constructor con diez campos es ilegible e inflexible. El patrón también resuelve el antipatrón Telescoping Constructor, donde el número de sobrecargas del constructor crece exponencialmente.
Estructura de Builder incluye una clase interna estática Builder con campos que reflejan los campos de la clase principal. Cada set-método devuelve Builder (this) para encadenamiento fluido. El método final build() crea el objeto destino pasando los valores de los campos a un constructor privado. La clase principal tiene un constructor privado que acepta un Builder. Cliente: Object.builder().setField1(val1).setField2(val2).build().
Cuándo usar Builder — objetos con 5+ campos donde solo 2-3 son obligatorios. Objetos de configuración (RequestConfig, DatabaseConfig). Objetos con lógica compleja de validación durante la creación. Objetos que deben ser inmutables después de la creación. En Android, Builder se usa activamente en el SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Builder en Kotlin tiene dos enfoques: Builder clásico estilo Java (mediante una clase anidada) y Builder DSL estilo Kotlin (mediante una lambda con receptor). El Builder estilo Java es preferible para compatibilidad con Android y cuando se usa con código Java. El DSL builder es la forma idiomática de Kotlin: una función acepta una lambda dentro de la cual this es el contexto Builder donde se pueden asignar campos directamente.
// Builder clásico
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)
}
}
}
// Uso
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — una alternativa sin clase anidada. Una función builder acepta una lambda en el contexto de un objeto constructor. Esto es idiomático para Kotlin y no requiere write-fields. Los DSL builders se usan activamente en Ktor Client, kotlinx.serialization, Compose (Modifier). El DSL builder es incompatible con Java y no es adecuado para bibliotecas con API Java.
Builder en Swift — Swift no tiene un patrón Builder incorporado, pero la interfaz fluida se implementa fácilmente mediante métodos que devuelven Self. Cada método configura una propiedad y devuelve self. A diferencia de Kotlin, Swift no requiere una clase Builder separada — se puede devolver el propio objeto si es mutable durante el ensamblaje. Para objetos inmutables se usa una clase Builder anidada de forma similar 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 introdujo @resultBuilder — un mecanismo de lenguaje para la construcción declarativa de estructuras. SwiftUI, AttributedString, SceneBuilder usan result builders. Es una alternativa al Builder clásico: en lugar de una cadena de set-métodos, result builder usa un bloque de código con elementos que el compilador ensambla en un array o árbol. @ViewBuilder en SwiftUI es el ejemplo más conocido: dentro de body se puede escribir if, switch, ForEach, y el compilador construye una View a partir de las condiciones.
Telescoping Constructor — un antipatrón donde una clase tiene múltiples constructores sobrecargados con diferentes conjuntos de parámetros. Por ejemplo, tres constructores: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). Al crecer los parámetros, el número de constructores crece exponencialmente — para n campos opcionales se necesitan n! combinaciones. Builder resuelve este problema permitiendo especificar solo los campos necesarios.
| Característica | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Cantidad de código | Crecimiento exponencial | Crecimiento lineal | Mínimo |
| Legibilidad | Baja (¿qué parámetro es cuál?) | Alta (método + nombre) | Alta (nombre = valor) |
| Inmutabilidad | Inmutable | Inmutable | Inmutable |
| Compatibilidad Java | Completa | Completa | Ninguna (solo Kotlin) |
| Validación | En cada constructor | En build() — una vez | En init() |
Kotlin named arguments + valores por defecto — una alternativa elegante a Builder en proyectos puramente Kotlin. Los parámetros del constructor tienen valores por defecto, el cliente solo pasa los necesarios: HttpConfig(baseUrl = url, timeout = 15_000). El inconveniente es la imposibilidad de validar campos obligatorios en tiempo de compilación. Builder proporciona campos obligatorios a través del constructor Builder (baseUrl es obligatorio). Para bibliotecas Java, Builder sigue siendo el estándar de facto.
Builder en Android SDK — uno de los patrones más comunes en la biblioteca estándar. 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().
Por qué Google usa Builder — compatibilidad hacia atrás. Añadir un nuevo método a Builder no rompe el código existente. Si Google usara un constructor con 20 parámetros, cada nuevo campo requeriría una nueva sobrecarga. Builder permite añadir set-métodos durante años sin breaking changes. Por ejemplo, NotificationCompat.Builder añadió setBubbleMetadata() en Android 11 sin afectar al código existente.
Builder en bibliotecas Kotlin — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()), Navigation (NavOptionsBuilder). En proyectos Kotlin, Builder se combina a menudo con DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). El patrón sigue siendo relevante para APIs públicas donde importan la compatibilidad hacia atrás y la interoperabilidad con Java.
Preguntas frecuentes
Builder es excesivo para objetos con 1-3 campos — un constructor normal o data class es más claro. También es excesivo en proyectos Kotlin sin interoperabilidad Java, donde named arguments + valores por defecto resuelven la misma tarea de forma más simple. Builder está justificado para 5+ campos, validación compleja o APIs Java donde los named arguments no están disponibles.
Builder crea un objeto complejo paso a paso (configuración de campos), Factory crea un objeto completo por tipo o parámetros. Builder responde a la pregunta «¿cómo ensamblar?», Factory responde «¿qué crear?». Builder a menudo se combina con Factory: Factory selecciona el tipo, Builder configura los campos.
En SwiftUI, el rol de Builder lo realizan los result builders (@ViewBuilder, @SceneBuilder) y los modificadores de View (.font(), .padding()). No se necesita Builder clásico porque SwiftUI usa un enfoque declarativo y modificadores fluidos. Para componentes UIKit, Builder es útil: UIAlertController, URLRequest, NSAttributedString.
Builder normalmente no requiere seguridad de hilos ya que se usa en un solo hilo para ensamblar un objeto. Si Builder se usa en un entorno multihilo (caso raro), sincronice cada set-método y build(). Alternativa — Immutable Builder: cada set-método devuelve una nueva instancia de Builder con el campo modificado.
Retrofit.Builder es una API pública de biblioteca que debe funcionar sin un contenedor DI. Builder proporciona flexibilidad de configuración (baseUrl, convertidores, interceptores, adaptadores de llamada personalizados) sin dependencias de Dagger u otros DI. Dentro de una aplicación, DI puede crear Retrofit una vez mediante Builder, pero Builder mismo sigue siendo parte de la API pública de Retrofit.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también