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
[csharp] view plaincopy
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分手了,通過親朋好友(中間人)來介紹
[csharp] view plaincopy
public class Lily{
public Boy boy;
public Girl()
{
boy=BoyFactory.createBoy();
}
public void Kiss()
{
boy.Kiss();
}
}
親友介紹,固然是好。如果不滿意,儘管另外換一個好了。但是,親友BoyFactory經常是以Singleton的形式出現,不然就是,存在於Globals,無處不在,無處不能。實在是太繁瑣了一點,不夠靈活。我爲什麼一定要這個親友摻和進來呢?爲什麼一定要付給她介紹費呢?萬一最好的朋友愛上了我的男朋友呢?
3.父母包辦:一切交給父母,自己不用非吹灰之力,Lily在家只Kiss
[csharp] view plaincopy
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,支持構造器注入,屬性注入,方法注入如下圖所示
具體使用方法如下圖所示
[csharp] view plaincopy
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");
}
}
}
如果採用配置文件註冊的話
[html] view plaincopy
<?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>
配置的後臺代碼:
[csharp] view plaincopy
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容器創建的時候會被自動調用。
[csharp] view plaincopy
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容器,並在其中完成了上面定義的接口和對應實現類之間映射的類型匹配。
[html] view plaincopy
<?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屬性是否是空
[csharp] view plaincopy
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