1. 程式人生 > >理解依賴注入(IOC)和學習Unity

理解依賴注入(IOC)和學習Unity

IOC:英文全稱:Inversion of Control,中文名稱:控制反轉,它還有個名字叫依賴注入(Dependency Injection)。
作用:將各層的物件以鬆耦合的方式組織在一起,解耦,各層物件的呼叫完全面向介面。當系統重構的時候,程式碼的改寫量將大大減少。
理解依賴注入:
    當一個類的例項需要另一個類的例項協助時,在傳統的程式設計過程中,通常有呼叫者來建立被呼叫者的例項。然而採用依賴注入的方式,建立被呼叫者的工作不再由呼叫者來完成,因此叫控制反轉,建立被呼叫者的例項的工作由IOC容器來完成,然後注入呼叫者,因此也稱為依賴注入。
舉個有意思的例子(來源於網際網路)

假如我們要設計一個Girl和一個Boy類,其中Girl有Kiss方法,即Girl想要Kiss一個Boy,首先問題是Girl如何認識Boy?     在我們中國常見的MM認識GG的方式有以下幾種:     A 青梅竹馬    B 親友介紹   C 父母包辦     哪一種是最好的?

1.青梅竹馬:很久很久以前,有個有錢的地主家的一閨女叫Lily,她老爸把她許配給縣太爺的兒子Jimmy,屬於指腹為婚,Lily非常喜歡kiss,但是隻能kiss Jimmy

public class Lily{  
        public Jimmy jimmy;   
        public Girl()  
        {  
            jimmy=new Jimmy();  
        }  
        public void Kiss()  
        {  
            jimmy.Kiss();  
        }  
    }  
  
    public class Jimmy  
    {  
        public void Kiss()  
        {  
            Console.WriteLine("kissing");  
        }  
    }  

這樣導致Lily對Jimmy的依賴性非常強,緊耦合。

2.親友介紹:經常Kiss同一個人令Lily有些厭惡了,她想嘗試新人,於是與Jimmy分手了,通過親朋好友(中間人)來介紹

public class Lily{  
        public Boy boy;   
  
        public Girl()  
        {  
            boy=BoyFactory.createBoy();  
        }  
        public void Kiss()  
        {  
            boy.Kiss();  
        }  
    }  

親友介紹,固然是好。如果不滿意,儘管另外換一個好了。但是,親友BoyFactory經常是以Singleton的形式出現,不然就是,存在於Globals,無處不在,無處不能。實在是太繁瑣了一點,不夠靈活。我為什麼一定要這個親友摻和進來呢?為什麼一定要付給她介紹費呢?萬一最好的朋友愛上了我的男朋友呢?

3.父母包辦:一切交給父母,自己不用非吹灰之力,Lily在家只Kiss

public class Lily{  
        public Boy boy;   
        public Girl(Boy boy)  
        {  
            this.boy=boy;  
        }  
        public void Kiss()  
        {  
            this.boy.Kiss();  
        }  
    }  

Well,這是對Girl最好的方法,只要想辦法賄賂了Girl的父母,並把Boy交給他。那麼我們就可以輕鬆的和Girl來Kiss了。看來幾千年傳統的父母之命還真是有用哦。至少Boy和Girl不用自己瞎忙乎了。這就是IOC,將物件的建立和獲取提取到外部。由外部容器提供需要的元件。

在設計模式中我們應該還知道依賴倒轉原則,應是面向介面程式設計而不是面向功能實現,好處是:多實現可以任意切換,我們的Boy應該是實現Kissable介面。這樣一旦Girl不想kiss可惡的Boy的話,還可以kiss可愛的kitten和慈祥的grandmother

好在.net中微軟有一個輕量級的IoC框架Unity,支援構造器注入,屬性注入,方法注入如下圖所示

具體使用方法如下圖所示

using System;  
  
using Microsoft.Practices.Unity;  
  
  
namespace ConsoleApplication9  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            //建立容器  
            IUnityContainer container=new UnityContainer();  
            //註冊對映  
            container.RegisterType<IKiss, Boy>();  
            //得到Boy的例項  
            var boy = container.Resolve<IKiss>();  
             
            Lily lily = new Lily(boy);  
            lily.kiss();  
        }  
    }  
  
  
    public interface IKiss  
    {  
        void kiss();  
    }  
      
  
    public class Lily:IKiss  
    {  
  
        public IKiss boy;   
  
        public Lily(IKiss boy)  
        {  
            this.boy=boy;  
        }  
        public void kiss()  
        {  
            boy.kiss();  
            Console.WriteLine("lily kissing");  
        }  
    }  
  
    public class Boy : IKiss  
    {  
        public void kiss()  
        {  
            Console.WriteLine("boy kissing");  
        }  
    }  
}  
如果採用配置檔案註冊的話
<?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="名稱空間.介面型別1,名稱空間" mapTo="名稱空間.實現型別1,名稱空間" />  
        <register type="名稱空間.介面型別2,名稱空間" mapTo="名稱空間.實現型別2,名稱空間" />  
      </container>  
    </containers>  
  </unity>  
</configuration>  

配置的後臺程式碼:
UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName)  
            as UnityConfigurationSection;  
configuration.Configure(container, "defaultContainer");  

可以通過方法ResolveAll來得到所有註冊物件的例項:
var Instances = container.Resolve<IKiss>();

Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中將具體依賴注入劃分為三種形式,即構造器注入、屬性(設定)注入和介面注入,習慣將其劃分為一種(型別)匹配和三種注入:

  • 型別匹配(Type Matching):雖然我們通過介面(或者抽象類)來進行服務呼叫,但是服務本身還是實現在某個具體的服務型別中,這就需要某個型別註冊機制來解決服務介面和服務型別之間的匹配關係;
  • 構造器注入(Constructor Injection):IoC容器會智慧地選擇選擇和呼叫適合的建構函式以建立依賴的物件。如果被選擇的建構函式具有相應的引數,IoC容器在呼叫建構函式之前解析註冊的依賴關係並自行獲得相應引數物件;
  • 屬性注入(Property Injection):如果需要使用到被依賴物件的某個屬性,在被依賴物件被建立之後,IoC容器會自動初始化該屬性;
  • 方法注入(Method Injection):如果被依賴物件需要呼叫某個方法進行相應的初始化,在該物件建立之後,IoC容器會自動呼叫該方法。

我們建立一個控制檯程式,定義如下幾個介面(IA、IB、IC和ID)和它們各自的實現類(A、B、C、D)。在型別A中定義了3個屬性B、C和D,其型別分別為介面IB、IC和ID。其中屬性B在構在函式中被初始化,以為著它會以構造器注入的方式被初始化;屬性C上應用了DependencyAttribute特性,意味著這是一個需要以屬性注入方式被初始化的依賴屬性;屬性D則通過方法Initialize初始化,該方法上應用了特性InjectionMethodAttribute,意味著這是一個注入方法在A物件被IoC容器建立的時候會被自動呼叫。

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;  
        }  
        [InjectionMethod]  
        public void Initalize(ID d)  
        {  
            this.D = d;  
        }  
    }  
    public class B : IB { }  
    public class C : IC { }  
    public class D : ID { }  

然後我們為該應用新增一個配置檔案,並定義如下一段關於Unity的配置。這段配置定義了一個名稱為defaultContainer的Unity容器,並在其中完成了上面定義的介面和對應實現類之間對映的型別匹配。
<?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>  
</configuration>  
最後在Main方法中建立一個代表IoC容器的UnityContainer物件,並載入配置資訊對其進行初始化。然後呼叫它的泛型的Resolve方法建立一個實現了泛型介面IA的物件。最後將返回物件轉變成型別A,並檢驗其B、C和D屬性是否是空
class Program  
    {  
        static void Main(string[] args)  
        {  
            UnityContainer container = new UnityContainer();  
            UnityConfigurationSection 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");  
            }  
        }  
    }  

從如下給出的執行結果我們可以得到這樣的結論:通過Resolve<IA>方法返回的是一個型別為A的物件,該物件的三個屬性被進行了有效的初始化。這個簡單的程式分別體現了介面注入(通過相應的介面根據配置解析出相應的實現型別)、構造器注入(屬性B)、屬性注入(屬性C)和方法注入(屬性D)

 a.B == null ? No
 a.C == null ? No
 a.D == null ? No