控制反轉(Ioc)和依賴注入(DI)

控制反轉IOC, 全稱 「Inversion of Control」。依賴注入DI, 全稱 「Dependency Injection」。編程

一個簡單的場景: 框架

當一個類的實例須要另外一個類的實例協助時,在傳統的程序設計過程當中,一般有調用者來建立被調用者的實例, 並使用。函數

面向的問題:測試

軟件開發中,爲了下降模塊間、類間的耦合度,提倡基於接口的開發,那麼在實現中必須面臨最終是有「誰」提供實體類的問題。(將各層的對象以鬆耦合的方式組織起來,各層對象的調用面向接口。)this

理解將組件的配置和使用分離: spa

若是以爲這句話比較抽象, 能夠將"組件"理解爲"對象"(底層組件),那麼相應的「組件的配置」就能夠理解成爲「對象的初始化」, 再來理解這句話,就是將對象的建立和使用分離. 其優勢很冥想, 將對象的建立延遲到部署階段(這句話也可能不太好理解), 就是說對象的建立所有依賴於統一的配置(聲明), 這樣咱們就能夠經過修改配置動態地把咱們指望使用的類替換成咱們打算使用的類,而不修改任何使用對象的原有代碼. 原則上,咱們須要把對象的裝配(配置/聲明)和業務代碼(使用)分離開來. 設計

軟件系統中的依賴(耦合):code

在採用面向對象設計的軟件系統中,萬物皆對象,全部的對象經過彼此的合做,完成整個系統的工做.就比如下面的齒輪,每一個齒輪轉動才能保證整個齒輪系統的運轉.可是這樣的設計就意味着強依賴,強耦合. 若是某個齒輪出現問題了(發生改變), 整個齒輪系統可能就會癱瘓, 這顯然是不能接受的. xml

什麼是控制反轉(IOC)? 對象

耦合關係不只會出如今對象與對象之間, 也會出如今軟件系統的各個模塊之間,以及軟件系統和硬件系統之間.如何下降系統之間, 模塊之間和對象之間的耦合度, 是軟件工程永遠追求的目標. 

爲了解決對象之間的耦合度太高的問題, 軟件專家Michael Mattson提出了IOC理論, 用來實現對象之間的"解耦". 目前這個理論已經被成熟的應用到項目開發中, 衍生出了各式各樣的IOC框架產品. 

IOC理論提出的大體觀點是這樣的: 藉助於"第三方"實現具備依賴關係的對象之間的解耦, 以下圖: 

因爲引進了中間位置的"第三方",這裏成爲IOC容器(Container), 使得A, B, C, D這4個對象沒有了耦合關係, 齒輪之間的傳動所有依靠"第三方"了, 換句話說, 所有對象的控制權所有上繳給"第三方"IOC容器, 

因此, IOC容器成了整個系統的關鍵核心, 它將對象之間的依賴關係下降到了最低程度. 

爲了在詳細說明其中的差異, 咱們來對比下: 在沒有引入IOC容器以前, 第一張圖中, 對象A依賴於對象B, 那麼A在初始化或者運行到某一點的時候, A本身必須主動建立對象B或者直接使用對象B. 而不管是建立仍是使用對象B, 控制權在A本身手上. 

軟件開發引入IOC容器以後, 這種狀況就徹底不一樣了, 第二章圖中, 因爲IOC容器的加入, 對象A和B之間失去了直接聯繫, 因此, 當對象A運行到須要對象B的時候, IOC容器主動建立一個對象B, 並將其注入到對象A須要的地方. 

經過先後的對比, 也就不難看出: 對象A得到依賴對象B的過程, 由主動行爲變爲被動行爲, 控制權發生轉換, 這就是"控制反轉"的由來. 

 

什麼是依賴注入(DI)?

2004年, Martin Fowler,在其著名的文章《Inversion of Control Containers and the Dependency Injection pattern》探討了同一個問題, 既然IOC是控制反轉, 那麼到底"那些方面的控制須要被反轉呢?' 通過詳細地分析和論證後, 

他得出了答案: 「得到依賴對象的過程須要反轉」. 控制被反轉以後, 得到依賴對象的過程由自身管理變爲了由IOC容器主動注入. 因而,他給"控制反轉"取了一個更適合的名稱叫作"依賴注入 Dependency Injection"。

他的這個答案, 實際上給出了實現IOC的方法: 依賴注入所謂依賴注入, 就是由IOC容器在運行期間, 動態地將某種依賴關係注入到對象之中. 

也就是說: 採用依賴注入的方式,建立被調用者的實例的工做再也不由調用者完成,而是由IOC容器來完成,這就是「控制反轉」的意思,而後,將其注入調用者,所以也稱爲 「依賴注入」。

 

咱們也能夠知曉, 控制反轉(IOC) 和 依賴注入(DI) 是從不一樣角度對同一件事務的描述. 就是經過IOC容器, 利用注入依賴關係的方式, 實現對象之間的解耦. 

 

Martin Fowler在文中將具體依賴注入劃分爲三種形式,即構造器注入、屬性(設置)注入和接口注入。

習慣將其劃分爲一種(類型)匹配和三種注入:

  • 類型匹配(Type Matching):雖然咱們經過接口(或者抽象類)來進行服務調用,可是服務自己仍是實如今某個具體的服務類型中,這就須要某個類型註冊機制來解決服務接口和服務類型之間的匹配關係;
  • 構造器注入(Constructor Injection):IoC容器會智能地選擇選擇和調用適合的構造函數以建立依賴的對象。若是被選擇的構造函數具備相應的參數,IoC容器在調用構造函數以前解析註冊的依賴關係並自行得到相應參數對象;
  • 屬性注入(Property Injection):若是須要使用到被依賴對象的某個屬性,在被依賴對象被建立以後,IoC容器會自動初始化該屬性;
  • 方法注入(Method Injection):若是被依賴對象須要調用某個方法進行相應的初始化,在該對象建立以後,IoC容器會自動調用該方法

建立一個控制檯程序,定義以下幾個接口(IA、IB、IC和ID)和它們的實現類(A、B、C、D)。在類型A中定義了三個屬性B、C和D,其參數類型分別爲IB、IC和ID。

其中,

屬性B做爲構函數的參數,認爲它會以構造器注入的方式被初始化 (??);

屬性C應用了DependencyAttribute特性,意味着這是一個須要以屬性注入方式被初始化的依賴屬性;

屬性D則經過方法Initialize初始化,該方法上應用了特性InjectionMethodAttribute, 意味着這是一個方法注入,在A對象被Ioc容器建立的時候,D會被自動調用。

 

Microsoft有一個輕量級的IoC框架Unity, 支持構造器注入,屬性注入,方法注入。對於C#語言,因爲語法元素上自己較其餘語言豐富許多,如何實施注入還有些技巧和特點之處。

下面介紹以下:

測試類:

namespace UnityDemo
{
    public interface IA { }
    public interface IB { }
    public interface IC { }
    public interface ID { }

    public class A : IA
    {
        public IB B { get; set; }

        [Dependency]
        public IC C { get; set; }
        public ID D { get; set; }

        public A(IB b)
        {
            this.B = b;
        // I am A } [InjectionMethod]
public void Initialize(ID d) { this.D = d; } } public class B : IB { // I am B } public class C : IC { // I am C } public class D : ID { // I am D } }

配置註冊:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
    </configSections>
    <unity>
        <containers>
            <container name="defaultContainer">
                <register type="UnityDemo.IA, UnityDemo" mapTo="UnityDemo.A, UnityDemo"/>
                <register type="UnityDemo.IB, UnityDemo" mapTo="UnityDemo.B, UnityDemo"/>
                <register type="UnityDemo.IC, UnityDemo" mapTo="UnityDemo.C, UnityDemo"/>
                <register type="UnityDemo.ID, UnityDemo" mapTo="UnityDemo.D, UnityDemo"/>
            </container>
        </containers>
    </unity>

    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
</configuration>

Main方法中,建立一個IOC容器的UnityContainer對象,並加載配置信息對其初始化,而後調用它的泛型的Resolve()方法建立一個實現了泛型接口IA的對象。

最後將返回對象轉換成類型A, 並逐一檢驗B,C和D屬性是否爲空,即初始化狀況。

namespace UnityDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            var container = new UnityContainer();
            var configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection;
            configuration.Configure(container, "defaultContainer");

            A a = container.Resolve<IA>() as A;
            if (null != a)
            {
                Console.WriteLine("a.B==null? {0}", a.B == null ? "Yes" : "No");
                Console.WriteLine("a.C==null? {0}", a.C == null ? "Yes" : "No");
                Console.WriteLine("a.D==null? {0}", a.D == null ? "Yes" : "No");
            }

        }
    }
}

 

執行結果:

 

分別體現了接口注入構造器注入(屬性B)屬性注入(屬性C)方法注入(屬性D)

 

依賴注入的技術點

IOC中最基本的技術就是"反射 (Reflection)" 編程. 關於反射的概念, 通俗地講代碼運行階段, 根據給出的信息動態的生成對象. 

 

JACK D. @ NJ USA

相關文章
相關標籤/搜索