整個應用程式只在一個地方把物件圖組裝起來——盡可能靠近進入點(
main、Startup)。這個「唯一組裝處」就是 Composition Root:其餘程式碼只依賴抽象,永遠不自己new會變的依賴。它不是某個類別或框架功能,而是一個架構位置。
🧠 When to Use
TIP
訊號:你想在測試或不同環境替換某個實作,卻發現「決定用哪個實作」的 new 散落在幾十個檔案裡——沒有一個集中的地方能一眼看出「這個系統是怎麼被組起來的」。
- 只要用了依賴注入,就需要某處把抽象接上具體——把那個「某處」收斂成唯一一點。
- 想把「組裝系統」這個基礎設施關注點,和「執行業務邏輯」徹底分開。
- 想讓切換實作(正式 DB ↔ 測試假物件、A 金流 ↔ B 金流)只改一處。
⚖️ Structure & Variants
最單純的形態 — main 就是天然的 Composition Root
fun main() {
// Composition Root:所有 `new`、所有「選哪個實作」都集中在這裡
val repository = SqlProductRepository(connectionString)
val service = ProductService(repository)
val command = UpdateCurrencyCommand(service)
command.execute() // 組裝完成,啟動整個流程
}- 所有具體型別的建立集中在最外層;
ProductService只認得ProductRepository介面。 - 組裝好頂層物件後呼叫其方法,應用程式就跑起來了。
核心原則(書中反覆強調的四條)
- 只有一個:整個應用程式應有且僅有一個 Composition Root。
- 越靠近進入點越好:
Program.Main、Startup、app 啟動事件。 - 唯一允許直接建立依賴的地方:其餘程式碼只透過抽象互動。
- 若用容器:Composition Root 是唯一可以參照容器的地方——在別處碰容器,就退化成 Service Locator 反模式。
框架把物件搶去建時 —— 找『接縫 (Seam)』
- 書中的關鍵洞察:物件組裝本身不難,難的是應用程式框架——框架常自己建立某些物件(Web 的 Controller、UI 的 Page),你無法直接用建構子注入。
- 解法是找到框架提供的接縫:允許你插入自訂建立邏輯的擴充點(如 ASP.NET Core 的
IControllerActivator)。 - 面對陌生框架先問:「它在哪裡建立我的物件?提供了什麼擴充點讓我介入?」找到接縫,就找到 Composition Root。
⚠️ Misuse & Anti-patterns
- 多個組裝點:東一塊西一塊地
new具體實作,等於沒有 Composition Root——這就是 Control Freak。 - 把容器傳遍全身:讓業務類別自己跟容器要依賴,Composition Root 名存實亡,變成 Service Locator。
- 被組裝爆炸嚇到:Composition Root 裡出現大量組裝碼是正常的(書稱 Apparent Dependency Explosion)——那只是把原本散落各處的隱性耦合集中顯現,不是新增複雜度。
- 組裝邏輯混入業務碼:Composition Root 只做「接線」,不放商業規則。
🔑 Takeaways
- Composition Root=唯一、靠近進入點的物件圖組裝處;其餘程式只依賴抽象。
- 它是概念/位置而非類別;用容器時是唯一能碰容器之處,否則退化成 Service Locator。
- 面對框架,關鍵是找到它建立物件的接縫。
- 組裝碼集中在此是特性不是臭味——它讓「系統怎麼組成」一目了然、換實作只改一處。
No notes yet — jot your takeaways or Q&A here.