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


控制反轉IOC, 全稱 “Inversion of Control”。依賴注入DI, 全稱 “Dependency Injection”。

一個簡單的場景: 

當一個類的實例需要另一個類的實例協助時,在傳統的程序設計過程中,通常有調用者來創建被調用者的實例, 並使用。

面向的問題:

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

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

如果覺得這句話比較抽象, 可以將"組件"理解為"對象"(底層組件),那么相應的“組件的配置”就可以理解成為“對象的初始化”, 再來理解這句話,就是將對象的創建和使用分離. 其優點很冥想, 將對象的創建延遲到部署階段(這句話也可能不太好理解), 就是說對象的創建全部依賴於統一的配置(聲明), 這樣我們就可以通過修改配置動態地把我們期望使用的類替換成我們打算使用的類,而不修改任何使用對象的原有代碼. 原則上,我們需要把對象的裝配(配置/聲明)和業務代碼(使用)分離開來. 

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

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

什么是控制反轉(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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM