說起IOC,可能很多初學者不知道是用來做什么的,今天正好有點時間,就來掃掃盲,順便鞏固下自己。
IOC全稱是Inversion Of Control,意為控制反轉(這些自然百度也有),可什么是控制反轉呢?
按我現在的理解,把上端依賴的項從細節轉換為抽象,並把細節轉移到第三方,這個就叫控制反轉。
怎么理解呢?最簡單的:我們有一個接口Ianimal,就像這樣:
public interface iAnimal { void talk(); }
然后我們現在用另一個類去實現該接口:
public class dog : iAnimal { public void talk() { Console.Write("小狗說:汪汪汪!"); } }
然后呢,往常來講,我們應該是這樣調用的:
iAnimal dog = new dog(); dog.talk(); Console.ReadKey();
運行結果應該是這樣的:
到目前為止都是正確的,相信各位小伙伴一般也是如此。
但請注意,我們在調用時是直接用dog類型new出來的,這種做法其實就已經依賴於細節了。
那么如何將這里依賴的細節修改為抽象呢?很簡單,我們只需要一個工廠類幫助我們生成實際的類即可。
但是有些小伙伴可能想,工廠內也有細節啊,難道寫個工廠就是IOC了么?
當然不是,我們還將細節交給配置文件(通過反射),這樣在功能變動時,無需修改原代碼,只需要修改配置文件即可。
工廠看上去是這樣的:
public class SimpleFactory { public static iAnimal CreateAnimal() { string classModule = ConfigurationManager.AppSettings["PhoneType"]; Assembly assembly = Assembly.Load(classModule.Split(',')[1]); Type type = assembly.GetType(classModule.Split(',')[0]); return (iAnimal)Activator.CreateInstance(type); } }
而我們的配置文件自然也要寫點東西了:
<appSettings> <add key="AnimalType" value="IOCandDI.dog,IOCandDI"/> </appSettings>
value內,逗號前是類的全限定名(命名空間+類名),后面是命名空間。
不會操作配置文件的小伙伴們,先添加這個引用:
然后引入System.Configuration命名空間就可以操作啦。
至於反射,直接引入System.Reflection 命名空間即可操作。反射的原理也很簡單,在這里就不再贅述。
修改過后的調用和以前的調用對比:
iAnimal dog = new dog(); dog.talk(); iAnimal obj = SimpleFactory.CreateAnimal(); obj.talk(); Console.ReadKey();
結果是一樣的:
這個時候,如果再多一種動物,只需要添加一個繼承iAnimal接口的類,並且修改配置文件即可。
IOC帶給我們的便利正是如此。
微軟也推出了一款IOC的插件,叫做Unity,你在NuGet程序包管理中很輕松就能找到它:
首先是下載與安裝:
然后在頁面引入Microsoft.Practices.Unity就可以操作了,基本流程如下:
IUnityContainer container = new UnityContainer();//聲明容器 container.RegisterType<iAnimal, cat>();//注冊類型 iAnimal obj = container.Resolve<cat>();//完成實例 obj.talk();
運行效果:
了解了Unity的基礎用法,我們在來看下如何利用Unity做依賴注入(DI——dependency injection)。
說到DI,就一定要知道DI的三種方式:屬性注入、構造注入、方法注入。我這里直接改造了cat類:
public class cat : iAnimal { [Dependency] public iColor color { get; set; } public iEat eat { get; set; } public iRun run { get; set; } [InjectionConstructor] public cat(iEat ieat) { eat = ieat; } public void talk() { Console.WriteLine("小貓說:喵喵喵!"); } [InjectionMethod] public void Happy(iRun irun) { run = irun; } }
大家一定發現了我分別在屬性、構造、方法頭上加了特性。
實際上,Dependency、InjectionConstructor、InjectionMethod就是Unity中DI操作的標識符,它們分別對應屬性注入、構造注入、方法注入。
那么,這些特性是如何幫助程序完成DI的呢?看調用代碼:
IUnityContainer container = new UnityContainer(); container.RegisterType<iAnimal, cat>(); container.RegisterType<iColor, Color>(); container.RegisterType<iEat, Eat>(); container.RegisterType<iRun, Run>(); iAnimal obj = container.Resolve<cat>(); obj.talk(); cat myCat = (cat)obj; Console.WriteLine("myCat.color是空的么?{0}", myCat.color == null); Console.WriteLine("myCat.eat是空的么?{0}", myCat.eat == null); Console.WriteLine("myCat.run是空的么?{0}", myCat.run == null); Console.ReadKey();
在Unity容器內類型注冊時,需注冊cat類內3個參數的類型,用來對應3種注入方式。
運行結果如下:
3種注入方式都成功了~添加了特性的屬性(或方法)在Unity容器執行實例時會自動尋找上面3個特性(的位置)並匹配注冊類型(完成注入)。
需要注意的是,構造注入無需聲明特性也可生效,Unity會自動尋找參數最多的構造參數進行注入(前提是被注入的參數類型一定要注冊)。
ADD:在和一位小伙伴討論時,他指出了我unity的使用方法有問題,我這里的確沒有將unity容器的細節抽出到配置文件中(因為想着是基礎使用教程就沒有加),這一方面我也懶得再寫,找到一篇介紹詳細的博文供大家參考——http://www.cnblogs.com/junchu25/archive/2012/08/10/2631455.html