在沒有任何 DI 庫的情況下使用依賴注入
我是 Repository 和 DI 的新手,並試圖在我的 MVC 5 項目中實現。
我實現了建構子注入,在我的控制器中有一個這樣的建構子:
IBook _ibook; public Test(IBook ibook) { _ibook = ibook; }沒有任何 DI 庫,它會拋出一個錯誤:沒有空的建構子。
為了避免這種情況,我又添加了一個建構子,如下所示:
public Test ():this(new Book()) { }由於我是 DI 新手,因此我不想通過使用 DI 庫來冒險我的項目,這可能會引發一些我可能無法解決的錯誤。
我想知道如果我不使用 DI 庫可能會遇到什麼問題。
如果推薦,哪個DI庫適合初學者?我看過一些 NInject 和 Unity 的影片。
將使用某種工具或庫的任何決定推遲到最後一個負責任的時刻是一個好主意。通過良好的設計,您可以稍後添加 DI 庫。這意味著您練習Pure DI。
MVC 中首選的攔截點是
IControllerFactory抽象,因為它允許您攔截 MVC 控制器的創建,並且這樣做可以防止您必須實現第二個建構子(這是一個反模式)。儘管可以使用IDependencyResolver,但使用該抽像不太方便,因為 MVC 也呼叫它來解決您通常不感興趣的事情。
IControllerFactory將作為您的組合根的自定義可以按如下方式實現:public sealed class CompositionRoot : DefaultControllerFactory { private static string connectionString = ConfigurationManager.ConnectionStrings["app"].ConnectionString; private static Func<BooksContext> bookContextProvider = GetCurrentBooksContext; private static IBookRepository bookRepo = new BookRepository(bookContextProvider); private static IOrderBookHandler orderBookHandler = new OrderBookHandler(bookRepo); protected override IController GetControllerInstance(RequestContext _, Type type) { // Unfortunately, because of MVC's design, controllers are not stateless, and // you will have to create them per request. if (type == typeof(OrderBookController)) return new HomeController(orderBookHandler); if (type == typeof(BooksController)) return new BooksController(bookRepo); // [other controllers here] return base.GetControllerInstance(_, type); } private static BooksContext GetCurrentBooksContext() { return GetRequestItem<BooksContext>(() => new BooksContext(connectionString)); } private static T GetRequestItem<T>(Func<T> valueFactory) where T : class { var context = HttpContext.Current; if (context == null) throw new InvalidOperationException("No web request."); var val = (T)context.Items[typeof(T).Name]; if (val == null) context.Items[typeof(T).Name] = val = valueFactory(); return val; } }您的新控制器工廠可以掛接到 MVC,如下所示:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { ControllerBuilder.Current.SetControllerFactory(new CompositionRoot()); // the usual stuff here } }當你練習 Pure DI 時,你通常會看到你的 Composition Root 由一大串
if語句組成。應用程序中每個根對像一個語句。從 Pure DI 開始有一些有趣的優勢。最突出的是編譯時支持,因為當您開始使用 DI 庫時,您將立即失去這一點。一些庫試圖通過允許您以編譯器可以執行的方式驗證您的配置來最小化這種損失;但是這個驗證是在執行時完成的,回饋週期永遠不會像編譯器給你的那麼短。
請不要試圖通過實現一些允許使用反射創建類型的機制來簡化開發,因為這樣做是在建構自己的 DI 庫。這樣做有很多缺點,例如,您失去了編譯時支持,而無法恢復現有 DI 庫可以給您帶來的任何好處。
當您的 Composition Root 開始難以維護時,您應該考慮從 Pure DI 切換到 DI 庫。
請注意,在我的範例 Composition Root 中,所有應用程序組件(控制器除外)都定義為singleton。單例意味著應用程序將只有每個組件的一個實例。這種設計需要你的組件是無狀態的(因此是執行緒安全的),任何有狀態的東西(例如
BooksContext)都不應該通過建構子注入。在範例中,我使用 aFunc<T>作為BooksContext每個請求儲存的提供者。使您的對像圖單例具有許多有趣的優點。例如,它可以防止您犯常見的配置錯誤,例如Captive Dependencies它迫使您進行更堅固的設計。此外,一些 DI 庫非常慢,將所有內容都設為單例可能會在以後切換到 DI 庫時防止出現性能問題。另一方面,這種設計的缺點是團隊中的每個人都應該明白所有組件都必須是無狀態的。在組件中儲存狀態會導致不必要的悲傷和惡化。我的經驗是,有狀態組件比大多數 DI 配置錯誤更容易檢測到。我還注意到,對於大多數開發人員來說,擁有單例組件是一件很自然的事情,尤其是那些沒有 DI 經驗的人。有關可供選擇的兩種組合模型及其優缺點的詳細討論,請查看此系列部落格文章。
請注意,在範例中,我為
BooksContext. 儘管所有 DI 庫都對范圍生活方式(例如按請求生活方式)提供開箱即用的支持,但我反對使用這些範圍生活方式(除非庫保證拋出異常而不是靜默失敗)。當您在活動範圍的上下文之外解析範圍實例時,大多數庫不會警告您(例如,在後台執行緒上解析每個請求的實例)。有些容器會返回一個單例實例,而另一些容器會在您每次詢問時返回一個新實例。這真的很麻煩,因為它隱藏了錯誤,並且可能會導致您嘗試調試您的應用程序很多小時(我在這裡根據經驗說話)。