什麼是Unity?
Unity是一個輕量級的可擴展的依賴注入容器,支持構造函數,屬性和方法調用注入。Unity可以處理那些從事基於組件的軟件工程的開發人員所面對的問題。構建一個成功應用程序的關鍵是實現非常松散的耦合設計。松散耦合的應用程序更靈活,更易於維護。這樣的程序也更容易在開發期間進行測試。你可以模擬對象,具有較強的具體依賴關系的墊片(輕量級模擬實現),如數據庫連接,網絡連接,ERP連接,和豐富的用戶界面組件。例如,處理客戶信息的對象可能依賴於其他對象訪問的數據存儲,驗證信息,並檢查該用戶是否被授權執行更新。依賴注入技術,可確保客戶類正確實例化和填充所有這些對象,尤其是在依賴可能是抽象的 。
關於IoC/DI
所謂控制反轉(IoC: Inversion Of Control)就是應用本身不負責依賴對象的創建和維護,而交給一個外部容器來負責。這樣控制權就由應用轉移到了外部IoC容器,控制權就實現了所謂的反轉。比如,在類型A中需要使用類型B的實例,而B實例的創建並不由A來負責,而是通過外部容器來創建。
有時我們又將IoC成為依賴注入(DI: Dependency Injection)。所謂依賴注入,就是由外部容器在運行時動態地將依賴的對象注入到組件之中。具體的依賴注入方式又包括如下三種典型的形式。
構造器注入(Constructor Injection):IoC容器會智能地選擇選擇和調用適合的構造函數以創建依賴的對象。如果被選擇的構造函數具有相應的參數,IoC容器在調用構造函數之前會自定義創建相應參數對象;
屬性注入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創建之後,IoC容器會自動初始化該屬性;
方法注入(Method Injection):如果被依賴對象需要調用某個方法進行相應的初始化,在該對象創建之後,IoC容器會自動調用該方法。
IPeople.cs
namespace UnityDemo { public interface IPeople { void DrinkWater(); } }
VillagePeople.cs
using System; namespace UnityDemo { public class VillagePeople : IPeople { IWaterTool _pw; public VillagePeople(IWaterTool pw) { _pw = pw; } public void DrinkWater() { Console.WriteLine(_pw.returnWater()); } } }
IWaterTool.cs
namespace UnityDemo { public interface IWaterTool { string returnWater(); } }
PressWater.cs
namespace UnityDemo { public class PressWater : IWaterTool { public string returnWater() { return "地下水好甜啊!!!"; } } }
Program.cs
using Microsoft.Practices.Unity; namespace UnityDemo { class Program { static void Main(string[] args) { UnityContainer container = new UnityContainer(); // 創建容器 container.RegisterType<UnityDemo.IWaterTool, UnityDemo.PressWater>(); // 注冊依賴對象 UnityDemo.IPeople people = container.Resolve<UnityDemo.VillagePeople>(); // 返回調用者 people.DrinkWater(); // 喝水 } } // 構造器注入 // 構造器注入(Constructor Injection):IoC容器會智能地選擇選擇和調用適合的構造函數以創建依賴的對象。 // 如果被選擇的構造函數具有相應的參數,IoC容器在調用構造函數之前解析注冊的依賴關系並自行獲得相應參數對象。 // RegisterType:可以看做是自來水廠決定用什麼作為水源,可以是水庫或是地下水,我只要“注冊”開關一下就行了。 // Resolve:可以看做是自來水廠要輸送水的對象,可以是農村或是城市,我只要“控制”輸出就行了。 }
屬性注入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創建之後,IoC容器會自動初始化該屬性。
VillagePeople.cs
using Microsoft.Practices.Unity; using System; namespace UnityDemo { public class VillagePeople : IPeople { // 屬性注入只需要在屬性字段前面加[Dependency]標記就行了 [Dependency] public IWaterTool _pw { get; set; } public void DrinkWater() { Console.WriteLine(_pw.returnWater()); } } }
調用方式和構造器注入一樣,通過
RegisterType< UnityDemo.IWaterTool, UnityDemo.PressWater>();注入就可以了,
除了使用RegisterType方法注冊,我們還可以在配置文件中注冊,[Dependency]和RegisterType方式其實都會產生耦合度,我們要添加一個屬性或是修改一中注冊都會去修改代碼,我們要做的就是代碼不去修改,只要修改配置文件了,這個在下面有講解,這邊就不多說,我們先看下使用UnityConfigurationSection的Configure方法加載配置文件注冊:
Program.cs
using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.Configuration; using System.Configuration; namespace UnityDemo { class Program { static void Main(string[] args) { UnityContainer container = new UnityContainer(); // 創建容器 UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName); configuration.Configure(container, "defaultContainer"); IPeople people = container.Resolve<IPeople>(); // 返回調用者 people.DrinkWater(); // 喝水 } } }
app.config
<?xml version="1.0"?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" /> </configSections> <unity> <containers> <container name="defaultContainer"> <register type="UnityContainerDemo.IPeople, UnityContainerDemo" mapTo="UnityContainerDemo.VillagePeople01, UnityContainerDemo"> <lifetime type="singleton" /> </register> <register type="UnityContainerDemo.IWaterTool, UnityContainerDemo" mapTo="UnityContainerDemo.PressWater, UnityContainerDemo"/> </container> </containers> </unity> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/> </startup> </configuration>
方法注入(Method Injection):如果被依賴對象需要調用某個方法進行相應的初始化,在該對象創建之後,IoC容器會自動調用該方法。
方法注入和屬性方式使用一樣,方法注入只需要在方法前加[InjectionMethod]標記就行了,從方法注入的定義上看,只是模糊的說對某個方法注入,並沒有說明這個方法所依賴的對象注入,所依賴的對象無非就三種:參數、返回值和方法內部對象引用,我們做一個示例試下:
VillagePeople03.cs
using Microsoft.Practices.Unity; namespace UnityDemo { class VillagePeople03 : IPeople { public IWaterTool tool; // 我是對象引用 public IWaterTool tool2; // 我是參數 public IWaterTool tool3; // 我是返回值 [InjectionMethod] public void DrinkWater() { if (tool == null) { } } [InjectionMethod] public void DrinkWater2(IWaterTool tool2) { this.tool2 = tool2; } public IWaterTool DrinkWater3() { return tool3; } } }
調用代碼:
public static void FuTest03() { UnityContainer container = new UnityContainer(); // 創建容器 UnityConfigurationSection configuration = (UnityConfigurationSection)ConfigurationManager.GetSection(UnityConfigurationSection.SectionName); configuration.Configure(container, "defaultContainer"); VillagePeople03 people = container.Resolve<IPeople>() as VillagePeople03;//返回調用者 Console.WriteLine("people.tool == null(引用) ? {0}", people.tool == null ? "Yes" : "No"); Console.WriteLine("people.tool2 == null(參數) ? {0}", people.tool2 == null ? "Yes" : "No"); Console.WriteLine("people.tool3 == null(返回值) ? {0}", people.tool3 == null ? "Yes" : "No"); }
container.Resolve<IPeople>() as VillagePeople03;其實多此一舉,因為已經在配置文件注冊過了,不需要再進行轉化,這邊只是轉化只是方便訪問VillagePeople03對象的幾個屬性值,我們看下運行效果:
結果不言而喻,其實我們理解的方法注入就是對參數對象的注入,從typeConfig節點-method節點-param節點就可以看出來只有參數的配置,而並沒有其他的配置,關於typeConfig下面會講到。
除了我們上面使用RegisterType和Resolve泛型方法,我們也可以使用非泛型注入,代碼如下:
public static void FuTest04() { UnityContainer container = new UnityContainer();//創建容器 container.RegisterType(typeof(IWaterTool), typeof(PressWater));//注冊依賴對象 IPeople people = (IPeople)container.Resolve(typeof(VillagePeople));//返回調用者 people.DrinkWater();//喝水 }
運行效果:
我們知道,Unity提供了對象的容器,那麼這個容器是如何進行索引的呢?也就是說,容器內的單元是如何標識的呢?在Unity中,標識主要有兩種方式, 一種是直接使用接口(或者基類)作為標識鍵,另一種是使用接口(或者基類)與名稱的組合作為標識鍵,鍵對應的值就是具體類。
第一種使用接口(或者基類)作為標識鍵:
container.RegisterType<IWaterTool, PressWater>();
代碼中的IWaterTool就是作為標識鍵,你可以可以使用基類或是抽象類作為標示,獲取注冊對象:container.Resolve<IWaterTool>(),如果一個Ioc容器容器裡面注冊了多個接口或是基類標示,我們再這樣獲取就不知道注冊的是哪一個?怎麼解決,就是用接口或是基類與名稱作為標識鍵,示例代碼如下:
public static void FuTest05() { UnityContainer container = new UnityContainer();//創建容器 container.RegisterType<IWaterTool, PressWater>("WaterTool1");//注冊依賴對象WaterTool1 container.RegisterType<IWaterTool, PressWater>("WaterTool2");//注冊依賴對象WaterTool2 IWaterTool wt = container.Resolve<IWaterTool>("WaterTool1");//返回依賴對象WaterTool1 var list = container.ResolveAll<IWaterTool>();//返回所有注冊類型為IWaterTool的對象 }
我們只需要在泛型方法RegisterType傳入一個名稱就可以來區分了(和注冊接口或基類),獲取的話也只要傳入注冊時候的名稱即可,我們看下list中的集合對象:
為了實現單例模式,我們通常的做法是,在類中定義一個方法如GetInstance,判斷如果實例為null則新建一個實例,否則就返回已有實例。但是我覺得這種做法將對象的生命周期管理與類本身耦合在了一起。所以我覺得遇到需要使用單例的地方,應該將生命周期管理的職責轉移到對象容器Ioc上,而我們的類依然是一個干淨的類,使用Unity創建單例代碼:
public static void FuTest07() { UnityContainer container = new UnityContainer();//創建容器 container.RegisterType<IWaterTool, PressWater>(new ContainerControlledLifetimeManager());//注冊依賴對象 IPeople people = container.Resolve<VillagePeople>();//返回調用者 people.DrinkWater();//喝水 }
上面演示了將IWaterTool注冊為PressWater,並聲明為單例,
ContainerControlledLifetimeManager字面意思上就是Ioc容器管理聲明周期,我們也可以不使用類型映射,將某個類注冊為單例:
container.RegisterType<PressWater>(new ContainerControlledLifetimeManager());
除了將類型注冊為單例,我們也可以將已有對象注冊為單例,使用RegisterInstance方法,示例代碼:
PressWater pw = new PressWater(); container.RegisterInstance<IWaterTool>(pw);
上面的代碼就表示將PressWater的pw對象注冊到Ioc容器中,並聲明為單例。
如果我們在注冊類型的時候沒有指定ContainerControlledLifetimeManager對象,Resolve獲取的對象的生命周期是短暫的,Ioc容器並不會保存獲取對象的引用,就是說我們再次Resolve獲取對象的時候,獲取的是一個全新的對象,如果我們指定ContainerControlledLifetimeManager,類型注冊後,我們再次Resolve獲取的對象就是上次創建的對象,而不是再重新創建對象,這也就是單例的意思。