Builder — създаващ модел, който позволява създаването на сложни обекти стъпка по стъпка. За разлика от конструктор с десет параметъра, Builder сглобява обекта чрез верига от извиквания, всяко от които настройва едно поле. Моделът е особено полезен за обекти с множество опционални параметри: конфигурация на мрежов клиент, настройки на база данни, строители на alert-ове и навигация. Повече на 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 (чрез ламбда с receiver). 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 — алтернатива без вложен клас. Функцията-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 + default values — елегантна алтернатива на 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()), Navigation (NavOptionsBuilder). В Kotlin проекти Builder често се комбинира с DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). Моделът остава актуален за публични API, където важни са обратната съвместимост и Java интероперабилността.
Често задавани въпроси
Builder е излишен за обекти с 1-3 полета — обикновен конструктор или data class са по-разбираеми. Също така е излишен в Kotlin проекти без Java интероп, където named arguments + default values решават същата задача по-просто. 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 adapter-и) без зависимости от Dagger или други DI. Вътре в приложението DI може да създаде Retrofit веднъж чрез Builder, но самият Builder остава част от публичния API на Retrofit.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също