依賴注入和 IDisposable
我對使用 Autofac
Dispose()的實現中的方法有點困惑IDisposable假設我對我的對像有一定的深度:
Controller取決於IManager;Manager取決於IRepository;Repository取決於ISession;ISession是IDisposable。這導致以下對像圖:
new Controller( new Manager( new Repository( new Session())));我是否需要讓我的 Manager 和 Repository 也實現 IDisposable 並在 Controller 中呼叫 Manager.Dispose()、在 Manager 中呼叫 Repository.Dispose() 等,或者 Autofac 會自動知道我的呼叫堆棧中的哪些對象需要正確處理?控制器對像已經是 IDisposable,因為它派生自基本 ASP.NET Web API 控制器
資源的一般規則是:
擁有資源的人有責任處置它。
這意味著如果一個類擁有一個資源,它應該在它創建它的相同方法中處理它(在這種情況下,一次性被稱為臨時一次性),或者如果這是不可能的,這通常意味著擁有類必須實現
IDisposable,因此它可以在其Dispose方法中處理資源。但重要的是要注意,一般來說,一個類只有在負責創建資源時才應該**擁有資源。但是當一個資源被注入時,這意味著這個資源在消費者之前就已經存在了。消費者沒有創建資源,在這種情況下不應該處置它。儘管您可以將資源的所有權傳遞給消費者(並在類的文件中傳達所有權已傳遞),但通常您不應傳遞所有權,因為這會使您的程式碼複雜化並使應用程序非常脆弱。
System.IO.StreamReader儘管轉移對象所有權的策略在某些情況下可能是有意義的,例如對於作為可重用 API 一部分的類型(如)。我將在下面解釋原因。因此,即使您
Controller依賴於需要處理的依賴項,您的控制器也不應該處理它們:
- 因為消費者沒有創建這樣的依賴,所以它不知道它的依賴的預期生命週期是多少。依賴關係應該比消費者更長壽。在這種情況下,讓消費者處理該依賴項將導致您的應用程序出現錯誤,因為下一個控制器將獲得已處理的依賴項,這將導致
ObjectDisposedException拋出一個。- 即使依賴者的生活方式與消費者的生活方式相同,處置仍然不是一個好主意,因為這會阻止您輕鬆地將該組件替換為將來可能具有更長壽命的組件。一旦您將該組件替換為一個壽命更長的組件,您將不得不遍歷它的所有消費者,這可能會導致整個應用程序發生徹底的變化。換句話說,您這樣做將違反開放/封閉原則——應該可以添加或替換功能而無需進行徹底的更改。
- 如果你的消費者能夠處理它的依賴,這意味著你
IDisposable在那個抽像上實現。這意味著這種抽象會洩露實現細節——這違反了依賴倒置原則。在抽像上實現時,您正在洩漏實現細節IDisposable,因為不太可能該抽象的每個實現都需要確定性處置,因此您在定義抽象時考慮了某個實現。消費者不必知道任何關於實現的資訊,無論它是否需要確定性處理。- 讓該抽象實現
IDisposable也會導致您違反介面隔離原則,因為抽象現在包含一個額外的方法(即Dispose),並非所有消費者都需要呼叫。他們可能不需要呼叫它,因為——正如我已經提到的——資源可能比消費者更長壽。在這種情況下讓它實現IDisposable是危險的,因為任何人都可以呼叫Dispose它導致應用程序崩潰。如果您對測試更嚴格,這也意味著您必須測試消費者是否不呼叫該Dispose方法。這將導致額外的測試程式碼。這是需要編寫和維護的程式碼。因此,您應該只讓實現實現
IDisposable. 這將抽象的任何消費者從是否應該呼叫的疑慮中解放出來Dispose(因為沒有Dispose方法可以呼叫抽象)。因為只有實現實現
IDisposable並且只有您的組合根創建此實現,所以組合根負責處理它。如果你的 DI 容器創建了這個資源,它也應該釋放它。Autofac 之類的 DI 容器實際上會為您執行此操作。您可以輕鬆地驗證這一點。如果您在不使用 DI 容器(又名Pure DI)的情況下連接對像圖,則意味著您必須自己在合成根中處理這些對象。考慮到您的問題中給出的對像圖,一個簡單的程式碼範例展示了解析(即組合)和釋放(即處置),如下所示:
// Create disposable component and hold reference to it var session = new Session(); // create the complete object graph including the disposable var controller = new Controller( new Manager( new Repository( session))); // use the object graph controller.TellYoMamaJoke(); // Clean up resources session.Dispose();當然,這個例子忽略了一些複雜的因素,比如實現確定性清理、與應用程序框架的集成以及 DI 容器的使用,但希望這段程式碼有助於繪製一個心智模型。
請注意,此設計更改使您的程式碼更簡單。實現
IDisposable抽象並讓消費者處理他們的依賴項將導致IDisposable像病毒一樣在您的系統中傳播並污染您的程式碼庫。它散佈開來,因為對於任何抽象,您總是可以想到需要清理其資源的實現,因此您必須IDisposable在每個抽像上實現。這意味著每個需要一個或多個依賴項的實現也必須實現IDisposable,這會級聯對像圖。這為系統中的每個類增加了大量程式碼和不必要的複雜性。