標籤:style class blog code http tar
- 為什麼需要依賴注入?
- 普通的ServiceUser來負責直接建立所需Service實現的執行個體方法.擁有以下的局限
- 在不同的環境下,ServiceProvider是千差萬別的(資料庫,臨時檔案,記憶體).
- 所以,不能將ServiceUser作為組件發布(適應不了各種差異環境).
- 為了將ServiceUser所在單元作為組件發布,必須滿足以下的條件.
- 將ServiceUser與具體的ServiceProvider_Imp解耦(解除編譯時間依賴).即不能出現new ServiceProvider_Imp()語句.
- 在運行時,根據環境來為ServiceUser來動態地提供ServiceProvider的實現執行個體.
- 總之,將ServiceUser和ServiceProvider之間的依賴,由編譯時間,延遲到運行時動態決定.
- 即在運行時"注入"這種依賴,稱為依賴注入(DI).
- ServiceUser可以看做架構.其需要對外發布,被其他模組使用.
- ServiceProvider可看做外掛程式.其在不同的環境下有差別.
- 依賴注入
- 在編譯時間,使用IServiceProvider介面.在運行時,再將具體的實作類別型綁定到ServiceUser上.
- 從而實現了服務的使用者(架構)和服務的提供者(外掛程式)的松耦合.
- 加入一個Assembler(容器)來完成對架構單獨發布所需的要求.
- 構造器DI
- 構造器指的是ServiceUser的構造器,也就是在構造ServiceUser的執行個體時,才把具體的ServiceProvider_實現傳遞給它.
1 class ServiceUser 2 { 3 IServiceProvider sp; 4 ServiceUser(IServiceProver sp) 5 { 6 this.sp = sp; 7 } 8 } 9 10 private MutablePicoContainer configureContainer() {11 MutablePicoContainer pico = new DefaultPicoContainer();12 //下面就是把ServiceProvider和ServiceUser都放入容器的過程,以後就由容器來提供ServiceUser的已完成依賴注入執行個體,13 //其中用到的執行個體參數和型別參數一般是從配置檔中讀取的,這裡是個簡單的寫法。14 Parameter[] finderParams = {new ConstantParameter("movies1.txt")};15 pico.registerComponentImplementation(IServiceProvider.class, ServiceProvider.class, finderParams);16 pico.registerComponentImplementation(ServiceUser.class); 17 //至此,容器裡面裝入了兩個類型,其中沒給出構造參數的那一個(ServiceUser)將依靠其在構造器中定義的傳入參數類型,在容器中18 //進行尋找,找到一個類型匹配項即可進行構造初始化。19 return pico; 20 }CtorDI
- 依賴被延遲到容器的構造過程中.
- 容器本身(具體來說是其構造過程)對ServiceUser和ServiceProvider都有依賴.
- 所以,ServiceUser,ServiceProvider,容器三者,沒有任何的依賴關係.
- 所有的依賴關係,所有的變化,都被封裝到了容器的構造中.
- 執行個體項目中,可以使用設定檔來將變化排斥到編譯期外.
- 容器含有一個類似GetInstance(Type t)的方法,在該方法中,容器調用ServiceUser的構造器,而使用的參數是在容器添加ServiceProvider時指定的(也就能夠在不同的環境下,使用不同的ServiceProvider的實現).
- 屬性DI
- 其與構造器DI的差別
- 在擷取對象執行個體時(GetInstance方法),前者通過反射得到待建立類型(ServiceUser)的構造器資訊,然後根據構造器的參數類型(ServiceProvider)在容器中進行尋找,然後構造出合適的執行個體.
- 而屬性DI是通過反射得到待建立類型的所有屬性,然後根據屬性的類型在容器中尋找對應類型的執行個體.
- 這種方式利於使用XML配置的方式來實現,所以諸如Spring的架構使用的都是這種方式.
- 介面DI
- 首先,在介面中定義需要注入的資訊.
- 然後,在ServiceUser中實現該介面.
- 最後,由容器調用介面定義的注入方法來完成注入.
- Service Locator
- 基本思想:有一個對象(服務定位器)知道如何擷取一個應用程式所需的所有服務(對象).
- 與DI的區別
- ServiceUser必須顯示調用Service Locator的方法來擷取對應的服務物件(ServiceProvider)執行個體.
- 而在DI中,這個過程是由容器隱式完成的.
- 由於ServiceUser和Servive Locator之間的依賴性,降低了模組之間的獨立性,所以IOC架構大多使用的是DI.
- 而Locator的優勢是實現簡單,所以當開發工作不複雜時,可以使用Service Locator.
- DI和Locator的對比
- 首先,兩者的目的都是解耦ServiceUser和ServiceProvider的實現.
- 核心區別在於,ServiceProvider的實現,以什麼方式提供給ServiceUser.
- 在Locator中,ServiceUser直接發送方法調用來請求具體的ServiceProvider實現的執行個體.
- 在DI中,ServiceUser不再需要任何的調用,由容器來隱式地完成將ServiceProvider的實現執行個體注入到ServiceUser中的過程.
- 控制反轉(IOC)是架構的基本特徵.
- 在選擇兩者的取捨時,主要考慮的是(ServiceUser)對Locator的依賴會不會造成問題.
- 首先,取決於ServiceUser的性質,如果有多個ServiceUser要使用同一服務,那麼可以使用Locator.
- 而如果要將ServiceUser作為組件發布供別人使用,而別人的Locator是不可預測的,很可能帶來不相容問題,所以要使用DI.
- 另一方面, 在DI中,容器和ServiceProvider的實現沒有依賴關係.除了設定檔資訊,服務實現無法擷取更多的關於容器的資訊.
- 使用DI模式,可以更清晰地理清組件間的依賴關係.
- 只需要觀察依賴注入機制(例如建構函式).就可以看到整個的依賴關係.
- 而使用Locator模式,必須在原始碼中搜尋所有對Locator的調用才可以.
- 測試
- 部分人會認為DI簡化了測試,因為可以非常簡單地在真實和偽組件(ServiceProvider的實現)之間切換.
- 但是,良好的Locator實現,應該能做到很容易地替換掉Locator.
- 也就是說,良好的架構應該支援簡單地使用一個偽組件來替換掉一個真實組件,來方便地進行測試.
- 所以,如果ServiceProvider的實現如果要脫離自己的控制,在另一個環境中使用,那麼就不能對Locator做任何的假定.
- 構造DI對比屬性DI
- 基準:應該在那裡填充欄位(屬性)的值?建構函式還是設定方法?
- 建構函式的一個好處是隱藏不可變的欄位.
- 構造DI的問題
- 參數個數過多
- 參數多是無法描述資訊的簡單類型.
- 對象含有多個建構函式,且有繼承關係.
- 代碼配置對比設定檔
- 在簡單的應用中,直接使用代碼來完成配置.
- 當設定檔變得越發複雜時,使用一種程式設計語言來編寫設定檔.
- 分離配置和使用
- 關鍵:服務的配置和使用應該被分離開.
- 實際上,這反映了一個基本的設計原則:分離介面與實現.
- 在物件導向的程式中,我們在一個地方根據條件來決定具體執行個體化哪一個子類,之後使用多態(介面類型)來操作該類型.
- DI的目的
- 用來降低主程式與對象間的關聯.
- 同時也能減低對象間的關聯.
- 簡化對象的建立動作,進而讓對象更加容易使用.