Builder — một mẫu tạo lập cho phép tạo các đối tượng phức tạp theo từng bước. Không giống như hàm tạo với hàng chục tham số, Builder lắp ráp một đối tượng thông qua một chuỗi các lời gọi, mỗi lời gọi cấu hình một trường. Mẫu này đặc biệt hữu ích cho các đối tượng có nhiều tham số tùy chọn: cấu hình máy khách mạng, cài đặt cơ sở dữ liệu, trình xây dựng cảnh báo và điều hướng. Tìm hiểu thêm tại Refactoring Guru: Builder.
Những điểm chính
Builder — một mẫu tạo lập GoF tách biệt việc xây dựng một đối tượng phức tạp khỏi biểu diễn của nó. Cùng một quy trình xây dựng có thể tạo ra các biểu diễn khác nhau. Builder hữu ích khi một đối tượng có nhiều tham số tùy chọn và hàm tạo với mười trường không đọc được và không linh hoạt. Mẫu này cũng giải quyết vấn đề phản mẫu Telescoping Constructor, nơi số lượng hàm tạo nạp chồng tăng theo cấp số nhân.
Cấu trúc Builder bao gồm một lớp Builder tĩnh bên trong với các trường phản ánh các trường của lớp chính. Mỗi phương thức set trả về Builder (this) để tạo chuỗi linh hoạt. Phương thức build() cuối cùng tạo đối tượng mục tiêu bằng cách chuyển các giá trị trường tới một hàm tạo riêng. Lớp chính có một hàm tạo riêng chấp nhận Builder. Máy khách: Object.builder().setField1(val1).setField2(val2).build().
Khi nào sử dụng Builder — các đối tượng có 5+ trường, trong đó chỉ có 2-3 trường bắt buộc. Các đối tượng cấu hình (RequestConfig, DatabaseConfig). Các đối tượng có logic xác thực phức tạp trong quá trình tạo. Các đối tượng phải bất biến (immutable) sau khi tạo. Trong Android, Builder được sử dụng tích cực trong SDK: AlertDialog.Builder, Retrofit.Builder, OkHttpClient.Builder, NotificationCompat.Builder.
Builder trong Kotlin có hai cách tiếp cận: Builder kiểu Java cổ điển (thông qua lớp lồng nhau) và Builder kiểu DSL Kotlin (thông qua lambda với bộ nhận). Builder kiểu Java được ưu tiên cho khả năng tương thích với Android và khi sử dụng với mã Java. DSL builder là cách thức tự nhiên của Kotlin: một hàm chấp nhận lambda bên trong đó this là ngữ cảnh Builder nơi các trường có thể được gán trực tiếp.
// Builder cổ điển
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)
}
}
}
// Sử dụng
val config = HttpConfig.Builder()
.baseUrl("https://api.example.com")
.timeout(15_000)
.header("Authorization", "Bearer token")
.build()
Kotlin DSL builder — một giải pháp thay thế không có lớp lồng nhau. Một hàm builder chấp nhận lambda trong ngữ cảnh của đối tượng xây dựng. Điều này là tự nhiên đối với Kotlin và không yêu cầu các trường write. DSL builders được sử dụng tích cực trong Ktor Client, kotlinx.serialization, Compose (Modifier). DSL builder không tương thích với Java và không phù hợp cho các thư viện có API Java.
Builder trong Swift — Swift không có mẫu Builder tích hợp sẵn, nhưng giao diện linh hoạt dễ dàng được triển khai thông qua các phương thức trả về Self. Mỗi phương thức cấu hình một thuộc tính và trả về self. Không giống như Kotlin, Swift không yêu cầu một lớp Builder riêng biệt — bạn có thể trả về chính đối tượng đó nếu nó có thể thay đổi trong quá trình lắp ráp. Đối với các đối tượng bất biến, một lớp Builder lồng nhau được sử dụng tương tự như 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 đã giới thiệu @resultBuilder — một cơ chế ngôn ngữ để xây dựng cấu trúc khai báo. SwiftUI, AttributedString, SceneBuilder sử dụng result builders. Đây là một giải pháp thay thế cho Builder cổ điển: thay vì một chuỗi các phương thức set, result builder sử dụng một khối mã với các phần tử mà trình biên dịch tập hợp thành một mảng hoặc cây. @ViewBuilder trong SwiftUI là ví dụ nổi tiếng nhất: bên trong body có thể viết if, switch, ForEach, và trình biên dịch xây dựng View từ các điều kiện.
Telescoping Constructor — một phản mẫu nơi một lớp có nhiều hàm tạo nạp chồng với các bộ tham số khác nhau. Ví dụ, ba hàm tạo: HttpConfig(url), HttpConfig(url, timeout), HttpConfig(url, timeout, retries). Khi các tham số tăng lên, số lượng hàm tạo tăng theo cấp số nhân — với n trường tùy chọn cần n! tổ hợp. Builder giải quyết vấn đề này bằng cách cho phép chỉ định các trường cần thiết.
| Đặc điểm | Telescoping Constructor | Builder | Kotlin named args |
|---|---|---|---|
| Khối lượng mã | Tăng theo cấp số nhân | Tăng tuyến tính | Tối thiểu |
| Tính dễ đọc | Thấp (tham số nào là gì?) | Cao (phương thức + tên) | Cao (tên = giá trị) |
| Tính bất biến | Bất biến | Bất biến | Bất biến |
| Tương thích Java | Đầy đủ | Đầy đủ | Không có (chỉ Kotlin) |
| Xác thực | Trong mỗi hàm tạo | Trong build() — một lần | Trong init() |
Kotlin named arguments + giá trị mặc định — một giải pháp thay thế thanh lịch cho Builder trong các dự án Kotlin thuần túy. Các tham số của hàm tạo có giá trị mặc định, máy khách chỉ truyền những tham số cần thiết: HttpConfig(baseUrl = url, timeout = 15_000). Nhược điểm là không thể xác thực các trường bắt buộc tại thời điểm biên dịch. Builder cung cấp các trường bắt buộc thông qua hàm tạo Builder (baseUrl là bắt buộc). Đối với các thư viện Java, Builder vẫn là tiêu chuẩn thực tế.
Builder trong Android SDK — một trong những mẫu phổ biến nhất trong thư viện tiêu chuẩn. 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().
Tại sao Google sử dụng Builder — tương thích ngược. Thêm một phương thức mới vào Builder không phá vỡ mã hiện tại. Nếu Google sử dụng hàm tạo với 20 tham số, mỗi trường mới sẽ yêu cầu một lần nạp chồng mới. Builder cho phép thêm các phương thức set qua nhiều năm mà không có thay đổi phá vỡ. Ví dụ, NotificationCompat.Builder đã thêm setBubbleMetadata() trong Android 11 mà không ảnh hưởng đến mã hiện tại.
Builder trong thư viện Kotlin — Ktor (HttpClientBuilder), Coil (ImageRequest.Builder), Room (Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build()), Navigation (NavOptionsBuilder). Trong các dự án Kotlin, Builder thường được kết hợp với DSL: Room.databaseBuilder(context, AppDatabase.class, "db").fallbackToDestructiveMigration().build(). Mẫu này vẫn phù hợp cho các API công khai nơi tương thích ngược và khả năng tương tác Java là quan trọng.
Các câu hỏi thường gặp
Builder là quá mức đối với các đối tượng có 1-3 trường — một hàm tạo thông thường hoặc data class rõ ràng hơn. Nó cũng quá mức trong các dự án Kotlin không có tương tác Java, nơi named arguments + giá trị mặc định giải quyết cùng một nhiệm vụ đơn giản hơn. Builder là hợp lý cho 5+ trường, xác thực phức tạp hoặc API Java nơi named arguments không có sẵn.
Builder tạo một đối tượng phức tạp từng bước (cấu hình trường), Factory tạo toàn bộ đối tượng theo loại hoặc tham số. Builder trả lời câu hỏi «làm thế nào để lắp ráp?», Factory trả lời «tạo cái gì?». Builder thường được kết hợp với Factory: Factory chọn loại, Builder cấu hình các trường.
Trong SwiftUI, vai trò của Builder được thực hiện bởi result builders (@ViewBuilder, @SceneBuilder) và các bổ ngữ View (.font(), .padding()). Builder cổ điển không cần thiết vì SwiftUI sử dụng cách tiếp cận khai báo và các bổ ngữ linh hoạt. Đối với các thành phần UIKit, Builder rất hữu ích: UIAlertController, URLRequest, NSAttributedString.
Builder thường không yêu cầu an toàn luồng vì nó được sử dụng trong một luồng duy nhất để lắp ráp một đối tượng. Nếu Builder được sử dụng trong môi trường đa luồng (trường hợp hiếm), hãy đồng bộ hóa từng phương thức set và build(). Giải pháp thay thế — Immutable Builder: mỗi phương thức set trả về một phiên bản Builder mới với trường đã được sửa đổi.
Retrofit.Builder là một API thư viện công khai phải hoạt động mà không cần vùng chứa DI. Builder cung cấp tính linh hoạt trong cấu hình (baseUrl, bộ chuyển đổi, bộ chặn, bộ điều hợp cuộc gọi tùy chỉnh) mà không phụ thuộc vào Dagger hoặc các khung DI khác. Trong một ứng dụng, DI có thể tạo Retrofit một lần thông qua Builder, nhưng bản thân Builder vẫn là một phần của API công khai của Retrofit.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm