物件生命週期 (Object Lifetime)

★★★★Studied

FromDependency Injection

📚 From the Books

物件生命週期 (Object Lifetime)ConceptDI & IoC

負責組裝的一方(Composer——不論是 Composition Root 的手工組裝,還是容器)不只決定「怎麼組」,還決定每個依賴何時建立、何時釋放、被幾個人共用。這就是 Lifestyle:Singleton、Transient、Scoped 三選一,選錯會埋下難查的並行與過期資料 bug。

🧠 When to Use

TIP

訊號:兩個請求的資料互相污染、或某個服務在高負載下才偶發詭異狀態——很可能是某個依賴的 Lifestyle 配錯了,被跨請求或跨執行緒共用了不該共用的狀態。

⚖️ Structure & Variants

三種 Lifestyle
  • Singleton:一個實例、全應用程式共用。啟動時建、結束時銷毀。效能最好,但只適合無狀態或執行緒安全的服務,要小心共用狀態與並行。
  • Transient:每次要用都給一個新的。最簡單、最安全,不必擔心共用與執行緒;代價是較多配置與 GC 壓力。不確定選哪個時,Transient 是最安全的預設。
  • Scoped:在一個定義好的範圍內共用一個(典型是「一次 Web request」)。最適合 Unit of Work(同一 request 共用同一個 DbContext);範圍結束時一併釋放。
Pure DI 下用『程式碼結構』控制 Lifestyle
// Singleton:在最外層建一次,傳給所有需要它的人
val cache = CachingRepository(connectionString)

fun handleRequest() {
    val dbContext = DbContext(connectionString)          // Scoped:每個 request 一個
    val service   = ProductService(dbContext, cache)     // Transient:每次都新建
    service.execute()
}
  • 不需要容器也能管生命週期——建立的位置就決定了共用範圍:最外層建=Singleton、每次進 request 建=Scoped、每次組裝就 new=Transient。
釋放原則:消費者絕不 Dispose 被注入的依賴
  • 消費者不知道這個依賴是否被別人共用、也不知道它的 Lifestyle(可能是 Singleton)——擅自釋放會殃及他人
  • 釋放責任屬於建立者(Composer):誰建立、誰負責 use {}close()
// Composer 建立,Composer 負責釋放
SqlProductRepository(connectionString).use { repo ->
    ProductService(repo).execute()
}   // repo 在此正確釋放;ProductService 從不碰釋放

⚠️ Misuse & Anti-patterns

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading