Software Design - Principle - Inversion of Control (IoC)
Quick Chat
1. 如果你的程式中,有一個物件需要另一個物件,應該怎麼做?
- 直接由需求方 new 一個實例嗎?
2. 如果那個物件的建構方式很複雜呢?
- 例如,它可能需要多個參數,還需要一些初始化邏輯。
- 如果每次都在需求方進行這些操作,會不會重複又繁瑣?
3. 如果有多個需求方重複依賴這個物件呢?
- 這些需求方能共享同一個物件嗎?
- 如果共享,如何管理這個共用的實例?誰來負責它的生命週期?
4. 依賴管理是誰的責任?
- 需求方是否應該負責處理自己所有的依賴?
- 還是應該有一個「專門負責管理依賴」的角色?
- 如果有一個「外部機制」來幫你管理這些依賴,需求方只需要專注於自己的邏輯,不需要擔心如何建構物件,這樣會不會更簡單?
控制反轉 - 什麼反轉了?
控制反轉(Inversion of Control, IoC)就是為了解決這些問題而生的。它帶來了一個核心理念:
需求方不再自己去「控制」依賴的構建和配置,而是把這部分的「控制權」交給「外部機制」(通常是 IoC 容器)。
需求方只需專注於「接收」或「查詢」依賴,並專心「使用」它們。簡化需求方的職責,使其僅專注於處理業務邏輯,從而提升模組化與可維護性。
好處
依賴管理
- 通過外部機制統一管理依賴,開發者不需要在各處手動管理物件實例的生命週期與建構邏輯。
- 容器能管理共享的物件實例,有效避免重複建構,提高資源利用率。
- 將依賴配置集中於容器,能輕鬆應對需求變更。例如,替換某個服務的具體實現時,只需要調整配置,而非修改多處程式碼。
提升模組化與可維護性
- 需求方與具體實現之間的耦合度降低,使得每個模組能更容易地獨立開發、測試與替換。
提升測試便利性
- 透過依賴注入,需求方可以接收模擬物件(mock)或測試替身(stub),從而更輕鬆地撰寫單元測試。
- sample - c#
var mockStorageService = new Mock<IStorageService>(); mockStorageService.Setup(service => service.SaveOrder(It.IsAny<Order>())).Verifiable(); var orderService = new OrderService(mockStorageService.Object);
- sample - js
const mockStorageService = { saveOrder: (order) => { console.log(`Mock saving order ${order.id}`); }, }; const orderService = new OrderService(mockStorageService);
技術實現
組合根(Composition Root)
- 集中管理所有依賴的構建與組合,通常位於應用程式的進入點。
依賴尋找(Dependency Lookup)
- 需求方主動向容器請求所需的依賴。
依賴注入(Dependency Injection, DI)
- 容器將依賴主動注入至需求方的機制,例如透過建構子注入、屬性注入或方法注入。