Builder — 一种创建型模式,允许逐步创建复杂对象。与具有十个参数的构造函数不同,Builder通过链式调用组装对象,每个调用设置一个字段。该模式特别适用于具有多个可选参数的对象:网络客户端配置、数据库设置、警报和导航构建器。更多信息请访问Refactoring Guru: Builder。
要点
Builder(构建者)— 一种GoF创建型模式,它将复杂对象的构建与其表示分离。相同的构建过程可以创建不同的表示。当一个对象有许多可选参数,而具有十个字段的构造函数难以阅读且不灵活时,Builder非常有用。该模式还解决了Telescoping Constructor(望远镜构造函数)问题 — 一种反模式,其中构造函数重载的数量呈指数增长。
Builder结构包括一个内部静态Builder类,其字段复制主类的字段。每个set方法返回Builder(this)以实现流畅链式调用。最终的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风格Builder(通过嵌套类)和Kotlin风格DSL构建器(通过带接收者的lambda)。Java风格Builder在Android兼容性和与Java代码一起使用时更受青睐。DSL构建器 — 符合Kotlin习惯用法的方式:函数接受lambda,其中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构建器 — 无需嵌套类的替代方案。构建器函数在构建者对象的上下文中接受lambda。这对Kotlin来说符合习惯用法,并且不需要write字段。DSL构建器在Ktor Client、kotlinx.serialization、Compose(Modifier)中被广泛使用。DSL构建器不兼容Java,不适合具有Java API的库。
Swift Builder — Swift没有内置的Builder模式,但可以通过返回Self的方法轻松实现流畅接口。每个方法设置一个属性并返回self。与Kotlin不同,Swift不需要单独的Builder类 — 如果对象在组装阶段是可变的,则可以返回对象本身。对于不可变对象,使用类似于Kotlin的嵌套Builder类。
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的替代方案:result builder不使用set方法链,而是使用一个代码块,其中包含编译器收集到数组或树中的元素。SwiftUI中的@ViewBuilder是最著名的例子:在body内部可以写if、switch、ForEach,编译器从条件中构建View。
Telescoping Constructor — 一种反模式,其中一个类具有多个重载的构造函数,具有不同的参数集。例如,三个构造函数:HttpConfig(url)、HttpConfig(url, timeout)、HttpConfig(url, timeout, retries)。随着参数的增加,构造函数的数量呈指数增长 — 对于n个可选字段,需要n!种组合。Builder通过允许仅设置必要字段来解决此问题。
| 特性 | Telescoping Constructor | Builder | Kotlin命名参数 |
|---|---|---|---|
| 代码量 | 指数增长 | 线性增长 | 最小 |
| 可读性 | 低(哪个参数?) | 高(方法+名称) | 高(名称=值) |
| 不变性 | 不可变 | 不可变 | 不可变 |
| Java兼容性 | 完全 | 完全 | 否(仅Kotlin) |
| 验证 | 在每个构造函数中 | 在build()中 — 一次 | 在init()中 |
Kotlin命名参数 + 默认值 — 在纯Kotlin项目中优雅的Builder替代方案。构造函数参数有默认值,客户端只传递需要的参数:HttpConfig(baseUrl = url, timeout = 15_000)。缺点 — 无法在编译阶段验证必填字段。Builder通过Builder构造函数提供必填字段(baseUrl是必填的)。对于Java库,Builder仍然是事实上的标准。
Android SDK中的Builder — 标准库中最常见的模式之一。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方法而不会产生破坏性变更。例如,NotificationCompat.Builder在Android 11中添加了setBubbleMetadata(),而不影响现有代码。
Kotlin库中的Builder — Ktor(HttpClientBuilder)、Coil(ImageRequest.Builder)、Room(Room.databaseBuilder())、Navigation(NavOptionsBuilder)。在Kotlin项目中,Builder通常与DSL结合使用:Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()。对于向后兼容和Java互操作性重要的公共API,该模式仍然具有相关性。
常见问题
对于具有1-3个字段的对象,Builder是多余的 — 普通构造函数或data class更容易理解。在没有Java互操作性的Kotlin项目中也是多余的,因为命名参数+默认值可以更简单地解决相同的任务。Builder适用于5+字段、复杂验证或命名参数不可用的Java API。
Builder逐步创建一个复杂对象(设置字段),Factory根据类型或参数创建完整的对象。Builder回答「如何组装?」的问题,Factory回答「创建什么?」。Builder经常与Factory结合使用:Factory选择类型,Builder设置字段。
在SwiftUI中,Builder的角色由result builders(@ViewBuilder、@SceneBuilder)和View修饰符(.font()、.padding())承担。不需要经典的Builder,因为SwiftUI使用声明式方法和流畅修饰符。对于UIKit组件,Builder很有用:UIAlertController、URLRequest、NSAttributedString。
Builder通常不需要线程安全,因为它在一个线程中用于组装对象。如果在多线程环境中使用Builder(罕见情况),请同步每个set方法和build()。替代方案 — 不可变Builder:每个set方法返回一个具有修改字段的新Builder实例。
Retrofit.Builder是库的公共API,必须无需DI容器即可使用。Builder提供配置灵活性(baseUrl、转换器、拦截器、自定义调用适配器),而不依赖于Dagger或其他DI。在应用程序内部,DI可以通过Builder创建一次Retrofit,但Builder本身仍然是Retrofit公共API的一部分。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。