把「一步步組裝複雜物件」的過程從物件本身抽出來,讓同一套組裝步驟能產生不同表示,也讓呼叫端不必面對一長串難讀的建構子參數。
🧠 When to Use
TIP
訊號:你的建構子變成 Pizza(true, false, true, 12, null, "thin") 這種伸縮建構子 (telescoping constructor),參數一多就分不清誰是誰,而且很多是可選的。
- 物件欄位多、又有很多可選,用建構子重載會爆炸。
- 建立過程固定有先後步驟,但每步的細節可替換(同流程、不同產物)。
- 想要不可變物件,又要避免半成品狀態外洩。
⚖️ Structure & Variants
Fluent Builder — 鏈式呼叫逐步組裝
class Burger private constructor(
val bun: String, val patties: Int, val cheese: Boolean,
) {
class Builder(private val bun: String) { // 必填走建構子
private var patties = 1
private var cheese = false
fun patties(n: Int) = apply { patties = n } // 回傳 this → 可鏈接
fun cheese() = apply { cheese = true }
fun build() = Burger(bun, patties, cheese) // 最後才產出不可變物件
}
}
val b = Burger.Builder("brioche").patties(2).cheese().build()- 必填參數放 Builder 建構子、可選參數用鏈式方法;
build()一次組出不可變成品。
Kotlin 慣用對照 — named args / apply
- Kotlin 有具名參數 + 預設值,很多「可選欄位」場景根本不需要手寫 Builder:
Burger(bun = "brioche", patties = 2)。 - 需要的是配置式 DSL 時才寫 builder,搭配
apply { }或 trailing lambda 做出html { body { ... } }那種巢狀建構語法。
與 Abstract Factory / Prototype 的差別
- Builder:重點在怎麼一步步組一個複雜物件,回傳的是「最終成品」。
- Abstract Factory:重點在回傳「一族產品」,一次一個方法、不講組裝順序。
- Prototype:不組裝,直接複製既有物件當起點。
⚠️ Misuse & Anti-patterns
- 物件根本不複雜:兩三個欄位就寫一整個 Builder,是把簡單事情搞複雜;Kotlin 直接用具名參數即可。
- Builder 可產出非法物件:忘了在
build()做必填檢查/不變式驗證,等於用更多程式碼換來一樣脆弱的物件。 - 可變 Builder 被當成最終物件用:把還沒
build()的半成品到處傳遞,狀態漂浮、難以推理——Builder 的產物才該外流。
🔑 Takeaways
- 解決的是「伸縮建構子與複雜組裝」:把建立過程抽成步驟,產物多為不可變物件。
- 必填走建構子、可選走鏈式方法,
build()收尾並驗證不變式。 - 在 Kotlin 先問值不值得:具名參數能解決就別手寫 Builder,留給 DSL/分步組裝場景。
No notes yet — jot your takeaways or Q&A here.