C# Unity 依賴註入
看了挺長時間框架搭建,今天看到一篇博客突然頓悟了依賴註入!
控制反轉(Ioc)可以看成自來水廠,那自來水廠的運行就可以看作依賴註入(DI),Ioc是一個控制容器,DI就是這個容器的運行機制,有點像國家主席和總理的意思。
( Ioc 主要功能是提供各種方法,但是具體要提供什麽樣的方法就要靠 DI 依賴註入了。就像自來水廠雖然是提供水的,它控制著水在大眾視野中的提供者,但是自來水廠的水卻是靠自己的地下水或者是其他方提供的)
構造器註入(Constructor Injection):IoC容器會智能地選擇選擇和調用適合的構造函數以創建依賴的對象。如果被選擇的構造函數具有相應的參數,IoC容器在調用構造函數之前解析註冊的依賴關系並自行獲得相應參數對象。
1 /// <summary> 2 /// 人接口 3 /// </summary> 4 public interface IPeople 5 { 6 void DrinkWater(); 7 } 8 /// <summary> 9 /// 村民 10 /// </summary> 11 public class VillagePeople : IPeople 12 { 13 IWaterTool _pw;14 public VillagePeople(IWaterTool pw) 15 { 16 _pw = pw; 17 } 18 public void DrinkWater() 19 { 20 Console.WriteLine(_pw.returnWater()); 21 } 22 } 23 /// <summary> 24 /// 壓水井25 /// </summary> 26 public class PressWater : IWaterTool 27 { 28 public string returnWater() 29 { 30 return "地下水好甜啊!!!"; 31 } 32 } 33 /// <summary> 34 /// 獲取水方式接口 35 /// </summary> 36 public interface IWaterTool 37 { 38 string returnWater(); 39 }
簡單調用一下
1 static void Main(string[] args) 2 { 3 UnityContainer container = new UnityContainer();//創建容器 4 container.RegisterType<Test01.IWaterTool, Test01.PressWater>();//註冊依賴對象 5 Test01.IPeople people = container.Resolve<Test01.VillagePeople>();//返回調用者 6 people.DrinkWater();//喝水 7 }
關於RegisterType和Resolve我們可以用自來水廠的例子來說明,請看下面:
- RegisterType:可以看做是自來水廠決定用什麽作為水源,可以是水庫或是地下水,我只要“註冊”開關一下就行了。
- Resolve:可以看做是自來水廠要輸送水的對象,可以是農村或是城市,我只要“控制”輸出就行了。
屬性註入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創建之後,IoC容器會自動初始化該屬性。
屬性註入只需要在屬性字段前面加[Dependency]標記就行了,如下:
1 /// <summary> 2 /// 村民 3 /// </summary> 4 public class VillagePeople02 : IPeople 5 { 6 [Dependency] 7 public IWaterTool _pw { get; set; } 8 public void DrinkWater() 9 { 10 Console.WriteLine(_pw.returnWater()); 11 } 12 }
調用方式和構造器註入一樣,通過RegisterType<Test02.IWaterTool, Test02.PressWater>();註入就可以了,除了使用RegisterType方法註冊,我們還可以在配置文件中註冊,[Dependency]和RegisterType方式其實都會產生耦合度,我們要添加一個屬性或是修改一中註冊都會去修改代碼,我們要做的就是代碼不去修改,只要修改配置文件了,這個在下面有講解,這邊就不多說,我們先看下使用UnityConfigurationSection的Configure方法加載配置文件註冊:
1 <unity> 2 <containers> 3 <container name="defaultContainer"> 4 <register type="UnityContainerDemo.IWaterTool,UnityContainerDemo" mapTo="UnityContainerDemo.PressWater,UnityContainerDemo"/> 5 <register type="UnityContainerDemo.IPeople,UnityContainerDemo" mapTo="UnityContainerDemo.VillagePeople02,UnityContainerDemo"/> 6 </container> 7 </containers> 8 </unity>
1 public static void FuTest02() 2 { 3 UnityContainer container = new UnityContainer();//創建容器 4 UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName); 5 configuration.Configure(container, "defaultContainer"); 6 IPeople people = container.Resolve<IPeople>();//返回調用者 7 people.DrinkWater();//喝水 8 }
方法註入(Method Injection):如果被依賴對象需要調用某個方法進行相應的初始化,在該對象創建之後,IoC容器會自動調用該方法。
方法註入和屬性方式使用一樣,方法註入只需要在方法前加[InjectionMethod]標記就行了,從方法註入的定義上看,只是模糊的說對某個方法註入,並沒有說明這個方法所依賴的對象註入,所依賴的對象無非就三種:參數、返回值和方法內部對象引用,我們做一個示例試下:
1 /// <summary> 2 /// 村民 3 /// </summary> 4 public class VillagePeople03 : IPeople 5 { 6 public IWaterTool tool;//我是對象引用 7 public IWaterTool tool2;//我是參數 8 public IWaterTool tool3;//我是返回值 9 [InjectionMethod] 10 public void DrinkWater() 11 { 12 if (tool == null) 13 { } 14 } 15 [InjectionMethod] 16 public void DrinkWater2(IWaterTool tool2) 17 { 18 this.tool2 = tool2; 19 } 20 [InjectionMethod] 21 public IWaterTool DrinkWater3() 22 { 23 return tool3; 24 } 25 }
1 public static void FuTest03() 2 { 3 UnityContainer container = new UnityContainer();//創建容器 4 UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName); 5 configuration.Configure(container, "defaultContainer"); 6 VillagePeople03 people = container.Resolve<IPeople>() as VillagePeople03;//返回調用者 7 Console.WriteLine("people.tool == null(引用) ? {0}", people.tool == null ? "Yes" : "No"); 8 Console.WriteLine("people.tool2 == null(參數) ? {0}", people.tool2 == null ? "Yes" : "No"); 9 Console.WriteLine("people.tool3 == null(返回值) ? {0}", people.tool3 == null ? "Yes" : "No"); 10 }
container.Resolve<IPeople>() as VillagePeople03;其實多此一舉,因為已經在配置文件註冊過了,不需要再進行轉化,這邊只是轉化只是方便訪問VillagePeople03對象的幾個屬性值
除了我們上面使用RegisterType和Resolve泛型方法,我們也可以使用非泛型註入,代碼如下:
1 public static void FuTest04() 2 { 3 UnityContainer container = new UnityContainer();//創建容器 4 container.RegisterType(typeof(IWaterTool), typeof(PressWater));//註冊依賴對象 5 IPeople people = (IPeople)container.Resolve(typeof(VillagePeople01));//返回調用者 6 people.DrinkWater();//喝水 7 }
為了實現單例模式,我們通常的做法是,在類中定義一個方法如GetInstance,判斷如果實例為null則新建一個實例,否則就返回已有實例。但是我覺得這種做法將對象的生命周期管理與類本身耦合在了一起。所以我覺得遇到需要使用單例的地方,應該將生命周期管理的職責轉移到對象容器Ioc上,而我們的類依然是一個幹凈的類,使用Unity創建單例代碼:
1 public static void FuTest07() 2 { 3 UnityContainer container = new UnityContainer();//創建容器 4 container.RegisterType<IWaterTool, PressWater>(new ContainerControlledLifetimeManager());//註冊依賴對象 5 IPeople people = container.Resolve<VillagePeople01>();//返回調用者 6 people.DrinkWater();//喝水 7 }
上面演示了將IWaterTool註冊為PressWater,並聲明為單例,ContainerControlledLifetimeManager字面意思上就是Ioc容器管理聲明周期,我們也可以不使用類型映射,將某個類註冊為單例:
1 container.RegisterType<PressWater>(new ContainerControlledLifetimeManager());
除了將類型註冊為單例,我們也可以將已有對象註冊為單例,使用RegisterInstance方法,示例代碼:
1 PressWater pw = new PressWater(); 2 container.RegisterInstance<IWaterTool>(pw);
上面的代碼就表示將PressWater的pw對象註冊到Ioc容器中,並聲明為單例。
如果我們在註冊類型的時候沒有指定ContainerControlledLifetimeManager對象,Resolve獲取的對象的生命周期是短暫的,Ioc容器並不會保存獲取對象的引用,就是說我們再次Resolve獲取對象的時候,獲取的是一個全新的對象,如果我們指定ContainerControlledLifetimeManager,類型註冊後,我們再次Resolve獲取的對象就是上次創建的對象,而不是再重新創建對象,這也就是單例的意思。
這種配置都會產生耦合度,比如添加一個屬性註入或是方法註入都要去屬性或是方法前加[Dependency]和[InjectionMethod]標記,我們想要的依賴註入應該是去配置文件中配置,當系統發生變化,我們不應去修改代碼,而是在配置文件中修改,這才是真正使用依賴註入解決耦合度所達到的效果,先看下Unity完整的配置節點:
1 <?xml version="1.0"?> 2 <configuration> 3 <configSections> 4 <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, 5 Microsoft.Practices.Unity.Configuration" /> 6 </configSections> 7 <typeAliases> 8 <!--壽命管理器類型--> 9 <typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,Microsoft.Practices.Unity" /> 10 <typeAlias alias="external" type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager, Microsoft.Practices.Unity" /> 11 <!--用戶定義的類型別名--> 12 <typeAlias alias="IMyInterface" type="MyApplication.MyTypes.MyInterface, MyApplication.MyTypes" /> 13 <typeAlias alias="MyRealObject" type="MyApplication.MyTypes.MyRealObject, MyApplication.MyTypes" /> 14 <typeAlias alias="IMyService" type="MyApplication.MyTypes.MyService, MyApplication.MyTypes" /> 15 <typeAlias alias="MyDataService" type="MyApplication.MyTypes.MyDataService, MyApplication.MyTypes" /> 16 <typeAlias alias="MyCustomLifetime" type="MyApplication.MyLifetimeManager, MyApplication.MyTypes" /> 17 </typeAliases> 18 <unity> 19 <containers> 20 <container name="containerOne"> 21 <types> 22 <!--類型映射無一生-默認為“瞬時”--> 23 <type type="Custom.MyBaseClass" mapTo="Custom.MyConcreteClass" /> 24 <!--使用上面定義的別名類型的映射--> 25 <type type="IMyInterface" mapTo="MyRealObject" name="MyMapping" /> 26 <!--使用類型別名指定的終身--> 27 <type type="Custom.MyBaseClass" mapTo="Custom.MyConcreteClass"> 28 <lifetime type="singleton" /> 29 </type> 30 <type type="IMyInterface" mapTo="MyRealObject" name="RealObject"> 31 <lifetime type="external" /> 32 </type> 33 <!--使用完整的類型名指定終身經理--> 34 <!--的一生經理指定的任何初始化數據--> 35 <!--將要使用的默認類型轉換器轉換--> 36 <type type="Custom.MyBaseClass" mapTo="Custom.MyConcreteClass"> 37 <lifetime value="sessionKey" type="MyApplication.MyTypes.MyLifetimeManager,MyApplication.MyTypes" /> 38 </type> 39 <!--使用一個自定義TypeConverter的終身管理器初始化--> 40 <type type="IMyInterface" mapTo="MyRealObject" name="CustomSession"> 41 <lifetime type="MyCustomLifetime" value="ReverseKey" typeConverter="MyApplication.MyTypes.MyTypeConverter,MyApplication.MyTypes" /> 42 </type> 43 <!--對象在配置中定義的註入參數--> 44 <!--使用上面定義的別名類型的映射--> 45 <type type="IMyService" mapTo="MyDataService" name="DataService"> 46 <typeConfig extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, Microsoft.Practices.Unity.Configuration"> 47 <constructor> 48 <param name="connectionString" parameterType="string"> 49 <value value="AdventureWorks"/> 50 </param> 51 <param name="logger" parameterType="ILogger"> 52 <dependency /> 53 </param> 54 </constructor> 55 <property name="Logger" propertyType="ILogger" /> 56 <method name="Initialize"> 57 <param name="connectionString" parameterType="string"> 58 <value value="contoso"/> 59 </param> 60 <param name="dataService" parameterType="IMyService"> 61 <dependency /> 62 </param> 63 </method> 64 </typeConfig> 65 </type> 66 </types> 67 68 <instances> 69 <add name="MyInstance1" type="System.String" value="Some value" /> 70 <add name="MyInstance2" type="System.DateTime" value="2008-02-05T17:50:00" /> 71 </instances> 72 73 <extensions> 74 <add type="MyApp.MyExtensions.SpecialOne" /> 75 </extensions> 76 77 <extensionConfig> 78 <add name="MyExtensionConfigHandler" type="MyApp.MyExtensions.SpecialOne.ConfigHandler" /> 79 </extensionConfig> 80 </container> 81 </containers> 82 </unity> 83 <startup> 84 <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> 85 </startup> 86 </configuration>
配置過unity的朋友看一下可能就清楚,這邊我們再簡單說下:
- Unity的配置節的名稱為”Unity",節處理程序的類型為 Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,它包含在程序集Microsoft.Practices.Unity.Configuration 中,當前程序添加該程序集的引用。
- typeAliases管理生命周期類型,以及一些類型別名的設置,方便我們映射對象的編寫,比如同一個類型註冊多次,我們只要在typeAlias添加一個類型別名,這樣我們再添加這個類型映射的時候只要寫個別名就可以了。
- containers是容器container集合,我們可以配置多個容器類型,通過Name屬性就可以訪問,比如訪問defaultContainer容器代碼:configuration.Configure(container, "defaultContainer");
- container為容器管理,下面包含多個類型映射,我們平常使用的構造器註冊、屬性註冊和方法註冊,就可以在constructor、property、method節點進行配置。
資料來源 http://www.cnblogs.com/xishuai/p/3670292.html#xishuai_h8
C# Unity 依賴註入