依賴注入容器和Prism的基礎服務已經在本系列隨筆中提到過很多次,今天將其分離出來專門說一說
1, 為什麼要使用依賴注入容器
我們知道, 在Composite Application中各個模塊之間是松耦合的關系, 也就是在設計的時候盡可能地減少模塊間的依賴, 但無論如何從業務角度講, 他們之間總還是要相互通信與合作的. 所以依賴注入容器在這其中扮演了一個橋梁般的角色. 比如當創建某一個組件實例的時候, 其依賴於另外的一個組件或服務, 此時依賴注入容器會將其需要的信息"注入(Injection)"進去, 采用注入的方式就避免了直接引用之間的耦合.除此之外, 使用依賴注入容器還有以下幾方面的好處:
組件(Component)不必自己去定位其依賴項和維護依賴項的生命周期
替換組件的依賴項的具體實現時不會影響到組件代碼
由於依賴項比較容易Mock,所以組件變得更易測
由於系統所需要的新的服務很容易被添加到容器中,系統維護難度也降低了
2, Prism的依賴注入容器
打開Composite Application Library(CAL)的源代碼, 我們似乎沒有找到一個依賴注入容器的實現,而是找到了一個依賴注入容器外觀接口IContainerFacade,其利用了外觀模式來高層抽象了一個依賴注入容器的最最基本的功能"解析"(Resolve).
/// <summary> /// 定義一個簡單的,被 Composite Application Library所使用的依賴注入容器外觀. /// </summary> public interface IContainerFacade { /// <summary> /// 從容器中解析出指定類型的實例 /// </summary> /// <param name="type">從容器中獲取的對象的類型.</param> /// <returns>一個 <paramref name="type"/>類型的實例.</returns> object Resolve(Type type); /// <summary> /// 嘗試著從容器中解析出指定類型的實例 /// </summary> /// <param name="type">從容器中獲取的對象的類型.</param> /// <returns> /// 一個 <paramref name="type"/>類型的實例. /// 如果類型不能被解析則返回<see langword="null" /> 值. /// </returns> object TryResolve(Type type); }
事實上Patterns&Practices團隊在設計時為了讓Prism兼容更多的依賴注入容器(比如Spring.Net, Castle Windsor, Unity等等)而設計了這一接口,所以在Prism源代碼中用到的都是IContainerFacade接口而不是一個具體的容器.
但是,在默認情況下,Prism使用了另外一個開源項目"Unity"中的輕量級的可擴展的依賴注入容器來作為默認容器UnityContainer. 該容器實現了構造函數注入、屬性注入和方法注入三種注入方式。
關於Unity中的依賴注入容器,可以參考下面的這幾篇文章:
深入 Unity 1.x 依賴注入容器之一:入門
深入 Unity 1.x 依賴注入容器之二:初始化 Unity
深入 Unity 1.x 依賴注入容器之三:獲取對象
深入 Unity 1.x 依賴注入容器之四:依賴注入
由於Unity不支持TryResolve(Type type);方法,Prism使用了一個適配器UnityContainerAdapter來解決這一問題。
3, 依賴注入容器的性能開銷
由於將類型注冊到依賴注入容器,然後使用容器來解析(Resolve)出類型實例對象時,根本機制是"反射"(Reflection),所以開銷是比較大的, 那麼如果你需要頻繁地創建大量的實例的話,是否使用依賴注入容器是需要認真考慮的問題. 大量的依賴和很深的依賴嵌套也會導致性能問題. 另外,如果一個組件既沒有依賴其它組件或服務,也不為其他組件所依賴,那麼將其放到依賴注入容器中也是不明智的.
4, 是否注冊為單例
我們在向依賴注入容器注冊默認類型映射或注冊Instance時涉及到一個問題是:是否按照單例模式注冊. 如果是的話, 那麼每次都容器中解析出來的對象都是同一對象實例,否則每次都是重新New的一個實例. 一般說來,對於一些全局服務以及共享狀態等我們會將其注冊為單例模式, 對於那些其被每次都需要拿一個新的實例去注入的依賴項我們將其按照非單例模式的形式注冊.
5,使用IUnityContainer還是IContainerFacade
他們分別位於 Microsoft.Practices.Composite和Microsoft.Practices.Unity命名空間下, 雖然都可以作為容器的高層接口,但使用IUnityContainer基本上就意味著直接使用Unity容器(Unity項目中的那個依賴注入容器),而使用IContainerFacade則意味著你可以兼容格式各樣的容器.但,我們知道IContainerFacade基本上只提供了一個功能"解析",但就一個基本的依賴注入容器而言,往往不僅僅是這一個功能,比如至少還要有類型注冊、實例注冊等等。所以在大多數情況下推薦使用IUnityContainer,從開源項目"StockTraderRI”中我們可以看到這一點,除非是下面的情況之一:
你作為ISV(獨立軟件供應商,independent software vendor)編寫一些提供多容器支持的服務
你的服務被用到多容器的系統中
6, 代碼配置 還是 配置文件配置
關於這個問題,我引用一下Martin Fowler的一段話為大家提供一些參考:
代碼配置 vs. 配置文件另一個問題相對獨立,但也經常與其他問題牽涉在一起:如何配置服務的組裝,通過配置文件還是直接編碼組裝?對於大多數需要在多處部署的應用程序來說,一個單獨的配置文件會更合適。配置文件幾乎都是XML 文件,XML 也的確很適合這一用途。不過,有些時候直接在程序代碼中實現裝配會更簡單。譬如一個簡單的應用程序,也沒有很多部署上的變化,這時用幾句代碼來配置就比XML 文件要清晰得多。
與之相對的,有時應用程序的組裝非常復雜,涉及大量的條件步驟。一旦編程語言中的配置邏輯開始變得復雜,你就應該用一種合適的語言來描述配置信息,使程序邏輯變得更清晰。然後,你可以編寫一個構造器(builder)類來完成裝配工作。如果使用構造器的情景不止一種,你可以提供多個構造器類,然後通過一個簡單的配置文件在它們之間選擇。
我常常發現,人們太急於定義配置文件。編程語言通常會提供簡捷而強大的配置管理機制,現代編程語言也可以將程序編譯成小的模塊,並將其插入大型系統中。如果編譯過程會很費力,腳本語言也可以在這方面提供幫助。
通常認為,配置文件不應該用編程語言來編寫,因為它們需要能夠被不懂編程的系統管理人員編輯。但是,這種情況出現的幾率有多大呢?我們真的希望不懂編程的系統管理人員來改變一個復雜的服務器端應用程序的事務隔離等級嗎?只有在非常簡單的時候,非編程語言的配置文件才有最好的效果。如果配置信息開始變得復雜,就應該考慮選擇一種合適的編程語言來編寫配置文件。