Builder — основи патерну будівельник у мобільній розробці

Автор: IT Sectr Опубліковано: 2026-02-17 Час читання: 7 хв

Builder — породжуючий патерн, що дозволяє створювати складні об'єкти покроково. На відміну від конструктора з десятком параметрів, Builder збирає об'єкт через ланцюжок викликів, кожен з яких налаштовує одне поле. Патерн особливо корисний для об'єктів з безліччю опціональних параметрів: конфігурація мережевого клієнта, налаштування бази даних, білдери алертів та навігації. Детальніше — на Refactoring Guru: Builder.

Головне

  • Builder — покрокове конструювання об'єктів з розділенням процесу та результату
  • Fluent interface — ланцюжок викликів set()/with() для зручного налаштування
  • Незмінність — Builder створює готовий об'єкт, який не потребує сетерів
  • Зворотна сумісність — нові поля додаються в Builder без ламання клієнтів
  • Kotlin DSL vs Builder — Kotlin пропонує type-safe builders як альтернативу

Що таке 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.

Builder у Kotlin: класична та DSL-реалізація

Kotlin Builder має два підходи: класичний Java-style Builder (через вкладений клас) та Kotlin-style DSL builder (через лямбду з ресивером). Java-style Builder кращий для Android-сумісності та при використанні з Java-кодом. DSL builder — ідіоматичний Kotlin-спосіб: функція приймає лямбду, всередині якої this — контекст Builder, де можна безпосередньо присвоювати поля.

kotlin
// Класичний 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.

Builder у Swift: result builders та ланцюжки

Swift Builder — Swift не має вбудованого патерну Builder, але fluent interface легко реалізується через методи, що повертають Self. Кожен метод налаштовує властивість і повертає self. На відміну від Kotlin, Swift не потребує окремого Builder-класу — можна повертати сам об'єкт, якщо він mutable на етапі збірки. Для immutable об'єктів використовується вкладений Builder-клас за аналогією з Kotlin.

swift
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 з умов.

Builder vs Telescoping Constructor: порівняння підходів

Telescoping Constructor — антипатерн, при якому клас має безліч перевантажених конструкторів з різним набором параметрів. Наприклад, три конструктори: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). Зі зростанням параметрів кількість конструкторів зростає експоненційно — для n опціональних полів потрібно n! комбінацій. Builder вирішує цю проблему, дозволяючи задавати тільки потрібні поля.

ХарактеристикаTelescoping ConstructorBuilderKotlin named args
Кількість кодуЕкспоненційне зростанняЛінійне зростанняМінімальне
ЧитабельністьНизька (який параметр який?)Висока (метод + ім'я)Висока (ім'я = значення)
НезмінністьImmutableImmutableImmutable
Java-сумісністьПовнаПовнаНемає (тільки Kotlin)
ВалідаціяВ кожному конструкторіВ build() — один разВ init()

Kotlin named arguments + значення за замовчуванням — елегантна альтернатива Builder у чистих Kotlin-проектах. Параметри конструктора мають значення за замовчуванням, клієнт передає тільки потрібні: HttpConfig(baseUrl = url, timeout = 15_000). Недолік — неможливість валідації обов'язкових полів на етапі компіляції. Builder дає обов'язкові поля через конструктор Builder (baseUrl обов'язковий). Для Java-бібліотек Builder залишається стандартом де-факто.

Builder в Android SDK: AlertDialog, Retrofit, OkHttp

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 надлишковий?

Builder надлишковий для об'єктів з 1-3 полями — звичайний конструктор або data class зрозуміліший. Також надлишковий в Kotlin-проектах без Java-інтеропу, де named arguments + значення за замовчуванням вирішують ту ж задачу простіше. Builder виправданий для 5+ полів, складної валідації або Java API, де named arguments недоступні.

Чим Builder відрізняється від Factory?

Builder створює один складний об'єкт покроково (налаштування полів), Factory створює об'єкт цілком за типом або параметрами. Builder відповідає на питання «як зібрати?», Factory — «що створити?». Builder часто об'єднується з Factory: Factory вибирає тип, Builder налаштовує поля.

Чи потрібен Builder у SwiftUI?

У SwiftUI роль Builder виконують result builders (@ViewBuilder, @SceneBuilder) та модифікатори View (.font(), .padding()). Класичний Builder не потрібен, оскільки SwiftUI використовує декларативний підхід та fluent modifiers. Для UIKit-компонентів Builder корисний: UIAlertController, URLRequest, NSAttributedString.

Як зробити Builder потокобезпечним?

Builder зазвичай не потребує потокобезпечності, оскільки використовується в одному потоці для збірки об'єкта. Якщо Builder використовується в багатопотоковому середовищі (рідкісний випадок), синхронізуйте кожен set-метод та build(). Альтернатива — Immutable Builder: кожен set-метод повертає новий екземпляр Builder зі зміненим полем.

Чому Retrofit використовує Builder, а не DI?

Retrofit.Builder — публічний API бібліотеки, який повинен працювати без DI-контейнера. Builder дає гнучкість налаштування (baseUrl, конвертери, інтерцептори, кастомні call адаптери) без залежностей від Dagger або інших DI. Всередині застосунку DI може створити Retrofit один раз через Builder, але сам Builder залишається частиною публічного API Retrofit.

Підсумки

  • Builder — покрокове конструювання об'єктів з fluent interface
  • Kotlin Builder — класичний (вкладений клас) і DSL (лямбда з ресивером)
  • Swift Builder — вкладений клас або @resultBuilder для декларативного коду
  • Незмінність — Builder створює immutable об'єкти через приватний конструктор
  • Android SDK — AlertDialog, Retrofit, OkHttp, NotificationCompat — стандарт індустрії
  • Зворотна сумісність — додавання полів в Builder не ламає існуючий код
  • Альтернатива Kotlin — named arguments + значення за замовчуванням простіші для чистих Kotlin проектів

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також