Builder — породжуючий патерн, що дозволяє створювати складні об'єкти покроково. На відміну від конструктора з десятком параметрів, Builder збирає об'єкт через ланцюжок викликів, кожен з яких налаштовує одне поле. Патерн особливо корисний для об'єктів з безліччю опціональних параметрів: конфігурація мережевого клієнта, налаштування бази даних, білдери алертів та навігації. Детальніше — на Refactoring Guru: Builder.
Головне
Builder — породжуючий патерн GoF, який відокремлює конструювання складного об'єкта від його представлення. Один і той же процес побудови може створювати різні представлення. Builder корисний, коли об'єкт має багато опціональних параметрів, а конструктор з десятьма полями нечитабельний та негнучкий. Патерн також вирішує проблему Telescoping Constructor — антипатерну, де кількість перевантажень конструктора зростає експоненційно.
Структура Builder включає внутрішній статичний клас Builder з полями, що копіюють поля основного класу. Кожен set-метод повертає Builder (this) для fluent chaining. Фінальний метод build() створює цільовий об'єкт, передаючи значення полів у приватний конструктор. Основний клас має приватний конструктор, що приймає Builder. Клієнт: Object.builder().setField1(val1).setField2(val2).build().
Коли використовувати Builder — об'єкти з 5+ полями, з яких лише 2-3 обов'язкових. Конфігураційні об'єкти (RequestConfig, DatabaseConfig). Об'єкти зі складною логікою валідації при створенні. Об'єкти, які мають бути незмінними (immutable) після створення. В Android Builder активно використовується в SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Kotlin Builder має два підходи: класичний Java-style Builder (через вкладений клас) та Kotlin-style DSL builder (через лямбду з ресивером). Java-style Builder кращий для Android-сумісності та при використанні з Java-кодом. DSL builder — ідіоматичний Kotlin-спосіб: функція приймає лямбду, всередині якої this — контекст Builder, де можна безпосередньо присвоювати поля.
// Класичний 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)
}
}
}
// Використання
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — альтернатива без вкладеного класу. Функція-білдер приймає лямбду в контексті об'єкта-будівельника. Це ідіоматично для Kotlin і не потребує write-полів. DSL builders активно використовуються в Ktor Client, kotlinx.serialization, Compose (Modifier). DSL builder несумісний з Java і не підходить для бібліотек з Java API.
Swift Builder — Swift не має вбудованого патерну Builder, але fluent interface легко реалізується через методи, що повертають Self. Кожен метод налаштовує властивість і повертає self. На відміну від Kotlin, Swift не потребує окремого Builder-класу — можна повертати сам об'єкт, якщо він mutable на етапі збірки. Для immutable об'єктів використовується вкладений Builder-клас за аналогією з 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 ввів @resultBuilder — мовний механізм для декларативної побудови структур. SwiftUI, AttributedString, SceneBuilder використовують result builders. Це альтернатива класичному Builder: замість ланцюжка set-методів result builder використовує блок коду з елементами, які компілятор збирає в масив або дерево. @ViewBuilder у SwiftUI — найвідоміший приклад: всередині body можна писати if, switch, ForEach, і компілятор будує View з умов.
Telescoping Constructor — антипатерн, при якому клас має безліч перевантажених конструкторів з різним набором параметрів. Наприклад, три конструктори: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). Зі зростанням параметрів кількість конструкторів зростає експоненційно — для n опціональних полів потрібно n! комбінацій. Builder вирішує цю проблему, дозволяючи задавати тільки потрібні поля.
| Характеристика | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Кількість коду | Експоненційне зростання | Лінійне зростання | Мінімальне |
| Читабельність | Низька (який параметр який?) | Висока (метод + ім'я) | Висока (ім'я = значення) |
| Незмінність | Immutable | Immutable | Immutable |
| Java-сумісність | Повна | Повна | Немає (тільки Kotlin) |
| Валідація | В кожному конструкторі | В build() — один раз | В init() |
Kotlin named arguments + значення за замовчуванням — елегантна альтернатива Builder у чистих Kotlin-проектах. Параметри конструктора мають значення за замовчуванням, клієнт передає тільки потрібні: HttpConfig(baseUrl = url, timeout = 15_000). Недолік — неможливість валідації обов'язкових полів на етапі компіляції. Builder дає обов'язкові поля через конструктор Builder (baseUrl обов'язковий). Для Java-бібліотек Builder залишається стандартом де-факто.
Builder в Android SDK — один з найпоширеніших патернів у стандартній бібліотеці. 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().
Чому Google використовує Builder — зворотна сумісність. Додавання нового методу в Builder не ламає існуючий код. Якби Google використовував конструктор із 20 параметрами, кожне нове поле вимагало б нового перевантаження. Builder дозволяє додавати set-методи роками без breaking changes. Наприклад, NotificationCompat.Builder додав setBubbleMetadata() в Android 11, не зачепивши існуючий код.
Builder у Kotlin-бібліотеках — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()), Navigation (NavOptionsBuilder). У Kotlin-проектах Builder часто комбінується з DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). Патерн залишається актуальним для публічних API, де важлива зворотна сумісність та Java-інтероп.
Часто задавані питання
Builder надлишковий для об'єктів з 1-3 полями — звичайний конструктор або data class зрозуміліший. Також надлишковий в Kotlin-проектах без Java-інтеропу, де named arguments + значення за замовчуванням вирішують ту ж задачу простіше. Builder виправданий для 5+ полів, складної валідації або Java API, де named arguments недоступні.
Builder створює один складний об'єкт покроково (налаштування полів), Factory створює об'єкт цілком за типом або параметрами. Builder відповідає на питання «як зібрати?», Factory — «що створити?». Builder часто об'єднується з Factory: Factory вибирає тип, Builder налаштовує поля.
У SwiftUI роль Builder виконують result builders (@ViewBuilder, @SceneBuilder) та модифікатори View (.font(), .padding()). Класичний Builder не потрібен, оскільки SwiftUI використовує декларативний підхід та fluent modifiers. Для UIKit-компонентів Builder корисний: UIAlertController, URLRequest, NSAttributedString.
Builder зазвичай не потребує потокобезпечності, оскільки використовується в одному потоці для збірки об'єкта. Якщо Builder використовується в багатопотоковому середовищі (рідкісний випадок), синхронізуйте кожен set-метод та build(). Альтернатива — Immutable Builder: кожен set-метод повертає новий екземпляр Builder зі зміненим полем.
Retrofit.Builder — публічний API бібліотеки, який повинен працювати без DI-контейнера. Builder дає гнучкість налаштування (baseUrl, конвертери, інтерцептори, кастомні call адаптери) без залежностей від Dagger або інших DI. Всередині застосунку DI може створити Retrofit один раз через Builder, але сам Builder залишається частиною публічного API Retrofit.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.