C#使用Unity實(shí)現(xiàn)IOC
一、什么是IOC
學(xué)習(xí)IOC之前先來(lái)了解一個(gè)依賴(lài)導(dǎo)致原則(DIP),依賴(lài)導(dǎo)致原則是IOC的核心原理。
依賴(lài)導(dǎo)致:即上層模塊不應(yīng)該依賴(lài)于低層模塊,二者應(yīng)該通過(guò)抽象來(lái)依賴(lài)。依賴(lài)于抽象,而不是依賴(lài)于細(xì)節(jié)。
首先來(lái)看下面的例子:
1、定義一個(gè)接口,封裝數(shù)據(jù)庫(kù)的基本CRUD操作,接口定義如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Data; namespace DataBase.Interface { /// <summary> /// 數(shù)據(jù)訪問(wèn)接口 /// </summary> public interface IDbInterface { string Insert(); string Delete(); string Update(); string Query(); } }
2、定義一個(gè)MSSQL類(lèi)實(shí)現(xiàn)該接口,用來(lái)模仿SQLServer操作,MSSQL類(lèi)定義如下:
using DataBase.Interface; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.MSSQL { public class DbMSSQL : IDbInterface { public string Delete() { return "MSSQL執(zhí)行刪除"; } public string Insert() { return "MSSQL執(zhí)行插入"; } public string Query() { return "MSSQL執(zhí)行查詢(xún)"; } public string Update() { return "MSSQL執(zhí)行更新"; } } }
3、定義一個(gè)Oracle類(lèi)實(shí)現(xiàn)該接口,模仿Oracle的操作,Oracle類(lèi)定義如下:
using DataBase.Interface; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.Oracle { public class DbOracle : IDbInterface { public string Delete() { return "Oracle執(zhí)行刪除"; } public string Insert() { return "Oracle執(zhí)行插入"; } public string Query() { return "Oracle執(zhí)行查詢(xún)"; } public string Update() { return "Oracle執(zhí)行更新"; } } }
4、定義一個(gè)控制臺(tái)應(yīng)用程序來(lái)調(diào)用:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using DataBase.Interface; using DataBase.MSSQL; namespace IOCConApp { class Program { static void Main(string[] args) { // 常規(guī)做法,即程序的上端,依賴(lài)于下端,依賴(lài)于細(xì)節(jié) DbMSSQL mssql = new DbMSSQL(); } } }
常規(guī)做法是添加引用,然后直接實(shí)例化類(lèi),但是這樣會(huì)依賴(lài)于細(xì)節(jié)實(shí)現(xiàn),現(xiàn)將代碼修改如下:
// 通過(guò)抽象來(lái)依賴(lài) IDbInterface dbInterface = new DbMSSQL();
但是這樣修改以后,雖然左邊是抽象了,但是右邊還是依賴(lài)于細(xì)節(jié)。
那就究竟什么是IOC呢?
IOC(Inversion of Control)即控制反轉(zhuǎn),是一個(gè)重要的面向?qū)ο缶幊痰姆▌t來(lái)消減程序之間的耦合問(wèn)題,把程序中上層對(duì)下層依賴(lài),轉(zhuǎn)移到一個(gè)第三方容器中來(lái)裝配。IOC是程序設(shè)計(jì)的目標(biāo),實(shí)現(xiàn)方式包含依賴(lài)注入和依賴(lài)查找,在.net中只有依賴(lài)注入。
說(shuō)到IOC,就不能不說(shuō)DI。DI:即依賴(lài)注入,是IOC的實(shí)現(xiàn)手段。
二、使用Unity實(shí)現(xiàn)IOC
Unity是一個(gè)IoC容器,用來(lái)實(shí)現(xiàn)依賴(lài)注入(Dependency Injection,DI),減少耦合的,Unity出自于偉大的微軟。
unity能夠做什么呢,列舉部分如下:
1.Unity支持簡(jiǎn)單對(duì)象創(chuàng)建,特別是分層對(duì)象結(jié)構(gòu)和依賴(lài),以簡(jiǎn)化程序代碼。其包含一個(gè)編譯那些可能存在依賴(lài)于其他對(duì)象的對(duì)象實(shí)例機(jī)制。
2.Unity支持必要的抽象,其允許開(kāi)發(fā)者在運(yùn)行時(shí)或配置去指定依賴(lài)關(guān)系同時(shí)可以簡(jiǎn)單的管理橫切點(diǎn)(AOP)。
3.Unity增加了推遲到容器組件配置的靈活性。其同樣支持一個(gè)容器層次的結(jié)構(gòu)。
4.Unity擁有服務(wù)定位能力,對(duì)于一個(gè)程序在許多情況下重復(fù)使用組件來(lái)分離和集中功能是非常有用的。
5.Unity允許客戶端儲(chǔ)存或緩存容器。對(duì)于在ASP.NET Web applications中開(kāi)發(fā)者將容器持久化于ASP.NET中的session或application中特別有效。
6.Unity擁有攔截能力,其允許開(kāi)發(fā)者通過(guò)創(chuàng)建并執(zhí)行handlers(在方法或?qū)傩员徽{(diào)用到達(dá)之前)來(lái)為已存在的組件增加一個(gè)函數(shù),并再次為返回調(diào)用結(jié)果。
7.Unity可以從標(biāo)準(zhǔn)配置系統(tǒng)中讀取配置信息,例如:XML文件,同時(shí)使用配置文件來(lái)配置容器。
8.Unity支持開(kāi)發(fā)者實(shí)現(xiàn)自定義容器擴(kuò)展,例如:你可以實(shí)現(xiàn)方法來(lái)允許額外的對(duì)象構(gòu)造和容器特征,例如緩存。
9.Unity允許架構(gòu)師和開(kāi)發(fā)者在現(xiàn)代化的程序中更簡(jiǎn)單的實(shí)現(xiàn)通用設(shè)計(jì)模式。
什么情況下要使用unity呢?
1.所構(gòu)建的系統(tǒng)依賴(lài)于健全的面向?qū)ο笤瓌t,但是大量不同的代碼交織在一起而難以維護(hù)。
2.構(gòu)建的對(duì)象和類(lèi)需要依賴(lài)其他對(duì)象或類(lèi)。
3.依賴(lài)于復(fù)雜的或需要抽象的對(duì)象。
4.希望利用構(gòu)造函數(shù)、方法或?qū)傩缘恼{(diào)用注入優(yōu)勢(shì)。
5.希望管理對(duì)象實(shí)例的生命周期。
6.希望能夠在運(yùn)行時(shí)管理并改變依賴(lài)關(guān)系。
7.希望在攔截方法或?qū)傩哉{(diào)用的時(shí)候生成一個(gè)策略鏈或管道處理容器來(lái)實(shí)現(xiàn)橫切(AOP)任務(wù)。
8.希望在Web Application中的回發(fā)操作時(shí)能夠緩存或持久化依賴(lài)關(guān)系。
1、程序中安裝Unity
使用管理NuGet程序包來(lái)安裝Unity,在項(xiàng)目上右鍵,選擇管理NuGet程序包:
在搜索框里面輸入U(xiǎn)nity,點(diǎn)擊右側(cè)安裝按鈕進(jìn)行安裝:
出現(xiàn)以下信息表示安裝成功:
2、使用Unity實(shí)現(xiàn)DI
先來(lái)看看最簡(jiǎn)單的Unity實(shí)現(xiàn)方式:
IUnityContainer container = new UnityContainer();//1、定義一個(gè)空容器 container.RegisterType<IDbInterface, DbMSSQL>();//2、注冊(cè)類(lèi)型,表示遇到IDbInterface的類(lèi)型,創(chuàng)建DbMSSQL的實(shí)例 var db = container.Resolve<IDbInterface>(); Console.WriteLine(db.Insert()); Console.ReadKey();
結(jié)果:
從結(jié)果中可以看出,db是DbMSSQL類(lèi)型的實(shí)例。
除了使用RegisterType注冊(cè)類(lèi)型以外,還可以注冊(cè)一個(gè)實(shí)例,例如:
// 使用RegisterInstance注冊(cè)IDbInterface的實(shí)例:new DbMSSQL() container.RegisterInstance<IDbInterface>(new DbMSSQL());
3、三種注入方式
三種注入方式:構(gòu)造函數(shù)注入、屬性注入、方法注入。
3.1 定義IHeadphone接口,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.Interface { public interface IHeadphone { } }
3.2 定義IMicrophone接口,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.Interface { public interface IMicrophone { } }
3.3 定義IPower接口,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.Interface { public interface IPower { } }
3.4 定義IPhone接口,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.Interface { public interface IPhone { void Call(); IMicrophone iMicrophone { get; set; } IHeadphone iHeadphone { get; set; } IPower iPower { get; set; } } }
3.5 分別實(shí)現(xiàn)上面定義的接口
IPhone接口的實(shí)現(xiàn)如下:
using DataBase.Interface; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Unity.Attributes; namespace DataBase.MSSQL { public class ApplePhone : IPhone { [Dependency]//屬性注入 public IMicrophone iMicrophone { get; set; } public IHeadphone iHeadphone { get; set; } public IPower iPower { get; set; } [InjectionConstructor]//構(gòu)造函數(shù)注入 public ApplePhone(IHeadphone headphone) { this.iHeadphone = headphone; Console.WriteLine("{0}帶參數(shù)構(gòu)造函數(shù)", this.GetType().Name); } public void Call() { Console.WriteLine("{0}打電話", this.GetType().Name); ; } [InjectionMethod]//方法注入 public void Init1234(IPower power) { this.iPower = power; } } }
IHeadphone接口的實(shí)現(xiàn)如下:
using DataBase.Interface; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.MSSQL { public class Headphone : IHeadphone { public Headphone() { Console.WriteLine("Headphone 被構(gòu)造"); } } }
IMicrophone接口的實(shí)現(xiàn)如下:
using DataBase.Interface; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.MSSQL { public class Microphone : IMicrophone { public Microphone() { Console.WriteLine("Microphone 被構(gòu)造"); } } }
IPower接口的實(shí)現(xiàn)如下:
using DataBase.Interface; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase.MSSQL { public class Power : IPower { public Power() { Console.WriteLine("Power 被構(gòu)造"); } } }
控制臺(tái)程序調(diào)用:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using DataBase.Interface; using DataBase.MSSQL; using Unity; namespace IOCConApp { /// <summary> /// IOC():控制反轉(zhuǎn),把程序上層對(duì)下層的依賴(lài),轉(zhuǎn)移到第三方的容器來(lái)裝配 /// 是程序設(shè)計(jì)的目標(biāo),實(shí)現(xiàn)方式包含了依賴(lài)注入和依賴(lài)查找(.net里面只有依賴(lài)注入) /// DI:依賴(lài)注入,是IOC的實(shí)習(xí)方式。 /// </summary> class Program { static void Main(string[] args) { #region MyRegion //// 常規(guī)做法,即程序的上端,依賴(lài)于下端,依賴(lài)于細(xì)節(jié) //DbMSSQL mssql = new DbMSSQL(); //// 通過(guò)抽象來(lái)依賴(lài) //IDbInterface dbInterface = new DbMSSQL(); //IUnityContainer container = new UnityContainer();//1、定義一個(gè)空容器 //container.RegisterType<IDbInterface, DbMSSQL>();//2、注冊(cè)類(lèi)型,表示遇到IDbInterface的類(lèi)型,創(chuàng)建DbMSSQL的實(shí)例 //var db = container.Resolve<IDbInterface>(); //// 使用RegisterInstance注冊(cè)IDbInterface的實(shí)例:new DbMSSQL() //container.RegisterInstance<IDbInterface>(new DbMSSQL()); //Console.WriteLine(db.Insert()); #endregion IUnityContainer container = new UnityContainer(); container.RegisterType<IPhone, ApplePhone>(); container.RegisterType<IMicrophone, Microphone>(); container.RegisterType<IHeadphone, Headphone>(); container.RegisterType<IPower, Power>(); IPhone phone = container.Resolve<IPhone>(); Console.WriteLine($"phone.iHeadphone==null? {phone.iHeadphone == null}"); Console.WriteLine($"phone.iMicrophone==null? {phone.iMicrophone == null}"); Console.WriteLine($"phone.iPower==null? {phone.iPower == null}"); Console.ReadKey(); } } }
輸出結(jié)果:
從輸出結(jié)果中可以看出三種注入方式的執(zhí)行順序:先執(zhí)行構(gòu)造函數(shù)注入,在執(zhí)行屬性注入,最后執(zhí)行方法注入。
注意:默認(rèn)情況下如果構(gòu)造函數(shù)上面沒(méi)有使用特性,那么默認(rèn)找參數(shù)最多的構(gòu)造函數(shù)執(zhí)行注入。
4、一個(gè)接口多個(gè)實(shí)現(xiàn)進(jìn)行注冊(cè)
如果多個(gè)不同的實(shí)例實(shí)現(xiàn)同一個(gè)接口,這種情況該怎么注冊(cè)呢?先來(lái)看看下面的代碼:
IUnityContainer container = new UnityContainer();//1、定義一個(gè)空容器 container.RegisterType<IDbInterface, DbMSSQL>();//2、注冊(cè)類(lèi)型,表示遇到IDbInterface的類(lèi)型,創(chuàng)建DbMSSQL的實(shí)例 container.RegisterType<IDbInterface, DbOracle>();//表示遇到IDbInterface的類(lèi)型,創(chuàng)建DbMSSQL的實(shí)例 var db = container.Resolve<IDbInterface>(); Console.WriteLine(db.Insert());
運(yùn)行結(jié)果:
從運(yùn)行結(jié)果中可以看出,后面注冊(cè)的類(lèi)型會(huì)把前面注冊(cè)的類(lèi)型給覆蓋掉,那么該如何解決呢?可以通過(guò)參數(shù)的方式來(lái)解決,代碼如下:
IUnityContainer container = new UnityContainer();//1、定義一個(gè)空容器 container.RegisterType<IDbInterface, DbMSSQL>("sql");//2、注冊(cè)類(lèi)型,表示遇到IDbInterface的類(lèi)型,創(chuàng)建DbMSSQL的實(shí)例 container.RegisterType<IDbInterface, DbOracle>("oracle");//表示遇到IDbInterface的類(lèi)型,創(chuàng)建DbMSSQL的實(shí)例 var sql = container.Resolve<IDbInterface>("sql"); var oracle = container.Resolve<IDbInterface>("oracle"); Console.WriteLine(sql.Insert()); Console.WriteLine(oracle.Insert());
運(yùn)行結(jié)果:
5、生命周期
生命周期及一個(gè)對(duì)象從創(chuàng)建到釋放中間經(jīng)過(guò)的時(shí)間。先看下面的代碼:
IUnityContainer container = new UnityContainer(); container.RegisterType<IDbInterface, DbMSSQL>(); IDbInterface db1 = container.Resolve<IDbInterface>(); IDbInterface db2 = container.Resolve<IDbInterface>(); Console.WriteLine("HashCode:"+db1.GetHashCode().ToString()); Console.WriteLine("HashCode:" + db2.GetHashCode().ToString()); Console.WriteLine(object.ReferenceEquals(db1,db2));
結(jié)果:
表明db1和db2是兩個(gè)不同的實(shí)例,即默認(rèn)情況下生命周期是瞬時(shí)的,每次都是創(chuàng)建一個(gè)新的實(shí)例。
container.RegisterType<IDbInterface, DbMSSQL>(new TransientLifetimeManager());表示是瞬時(shí)生命周期,默認(rèn)情況下即這種。
在看下面的代碼:
IUnityContainer container = new UnityContainer(); container.RegisterType<IDbInterface, DbMSSQL>(new ContainerControlledLifetimeManager()); IDbInterface db1 = container.Resolve<IDbInterface>(); IDbInterface db2 = container.Resolve<IDbInterface>(); Console.WriteLine("HashCode:" + db1.GetHashCode().ToString()); Console.WriteLine("HashCode:" + db2.GetHashCode().ToString()); Console.WriteLine(object.ReferenceEquals(db1, db2));
結(jié)果:
上圖的結(jié)果可以看出,db1和db2是同一個(gè)實(shí)例。
container.RegisterType(new ContainerControlledLifetimeManager())
表示是容器單例,每次都是同一個(gè)實(shí)例。
6、使用配置文件實(shí)現(xiàn)
在上面的例子中,所有的例子都是一直在依賴(lài)于細(xì)節(jié),那么怎么解決不依賴(lài)于細(xì)節(jié)呢?答案是只能使用配置文件,配置文件如下:
<configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Unity.Configuration"/> </configSections> <unity> <sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Unity.Interception.Configuration"/> <containers> <container name="testContainer"> <!--逗號(hào)前面是接口類(lèi)型的完全限定名:命名空間+接口名稱(chēng),逗號(hào)后面是DLL文件的名稱(chēng) name解決同一個(gè)接口不同實(shí)例問(wèn)題--> <register type="DataBase.Interface.IDbInterface,DataBase.Interface" mapTo="DataBase.MSSQL.DbMSSQL, DataBase.MSSQL" name="sql"/> <register type="DataBase.Interface.IDbInterface,DataBase.Interface" mapTo="DataBase.Oracle.DbOracle, DataBase.Oracle" name="oracle"/> </container> </containers> </unity> </configuration>
注意:這個(gè)一個(gè)單獨(dú)的配置文件,要把屬性里面的復(fù)制到輸出目錄改為始終復(fù)制,那么這個(gè)配置文件才會(huì)生成到Debug目錄里面。
程序如下:
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap(); fileMap.ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + "CfgFiles\\Unity.Config");//找配置文件的路徑 Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); IUnityContainer container = new UnityContainer(); section.Configure(container, "testContainer"); IDbInterface db = container.Resolve<IDbInterface>("sql"); Console.WriteLine(db.Insert());
結(jié)果:
觀察上面的代碼,會(huì)發(fā)現(xiàn),如果改成使用配置文件的方式實(shí)現(xiàn)的話,代碼里面就不會(huì)依賴(lài)于細(xì)節(jié)了,只要一個(gè)接口類(lèi)型。既然沒(méi)有細(xì)節(jié)了,那么對(duì)項(xiàng)目進(jìn)行如下的改造:把引用里面對(duì)細(xì)節(jié)的引用都去掉(即去掉DataBase.MSSQL和DataBase.Oracle),然后Debug文件夾里面沒(méi)有這兩個(gè)DLL了,但是這時(shí)需要把這兩個(gè)DLL復(fù)制到Debug目錄下面,否則程序運(yùn)行的時(shí)候會(huì)找不到具體實(shí)現(xiàn)的類(lèi)型。這樣就意味著程序架構(gòu)只依賴(lài)于接口。
引用里面只要對(duì)接口的引用了,沒(méi)有對(duì)具體實(shí)現(xiàn)的引用。去掉了對(duì)細(xì)節(jié)的依賴(lài)。
注意:使用配置文件實(shí)現(xiàn)時(shí),必須把接口的具體實(shí)現(xiàn)類(lèi)復(fù)制到程序目錄下面。
如果有額外添加了一種數(shù)據(jù)庫(kù),那么只需要修改配置文件,把新的實(shí)現(xiàn)類(lèi)復(fù)制到程序目錄下面即可實(shí)現(xiàn)程序的升級(jí)。
使用配置文件的時(shí)候,需要把UnityContainer容器定義為靜態(tài)的,這樣只需要讀取一次配置文件即可。
到此這篇關(guān)于C#使用Unity實(shí)現(xiàn)IOC的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
ASP.NET實(shí)現(xiàn)偽靜態(tài)網(wǎng)頁(yè)方法小結(jié)
這篇文章主要介紹了ASP.NET實(shí)現(xiàn)偽靜態(tài)網(wǎng)頁(yè)方法小結(jié),主要包括了利用Httphandler實(shí)現(xiàn)URL重寫(xiě)、地址重寫(xiě)、利用Mircosoft URLRewriter.dll實(shí)現(xiàn)頁(yè)面?zhèn)戊o態(tài)等,需要的朋友可以參考下2014-09-09將DataRow轉(zhuǎn)成指定類(lèi)型的類(lèi),并返回這個(gè)類(lèi)的對(duì)象(帶值)
由于實(shí)際需要 將DataRow轉(zhuǎn)成指定類(lèi)型的類(lèi),并返回這個(gè)類(lèi)的對(duì)象(帶值) ,實(shí)現(xiàn)方法看下面的代碼。2008-04-04ASP.NET MVC重寫(xiě)RazorViewEngine實(shí)現(xiàn)多主題切換
這篇文章主要為大家詳細(xì)介紹了ASP.NET MVC重寫(xiě)RazorViewEngine實(shí)現(xiàn)多主題切換,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06.net連接oracle的3種實(shí)現(xiàn)方法
這篇文章介紹了.net連接oracle的3種實(shí)現(xiàn)方法,有需要的朋友可以才可以一下2013-07-07ASP.NET Core文件壓縮常見(jiàn)使用誤區(qū)(最佳實(shí)踐)
本文給大家分享ASP.NET Core文件壓縮常見(jiàn)的三種誤區(qū),就每種誤區(qū)給大家講解的非常詳細(xì),是項(xiàng)目實(shí)踐的最佳紀(jì)錄,對(duì)ASP.NET Core文件壓縮相關(guān)知識(shí)感興趣的朋友一起看看吧2021-05-05Asp.Net 5分鐘實(shí)現(xiàn)網(wǎng)頁(yè)實(shí)時(shí)監(jiān)控
在項(xiàng)目開(kāi)發(fā)中經(jīng)常會(huì)用到監(jiān)控功能,下面通過(guò)本篇文章給大家介紹Asp.Net 5分鐘實(shí)現(xiàn)網(wǎng)頁(yè)實(shí)時(shí)監(jiān)控,需要的朋友可以參考下2017-12-12詳解免費(fèi)開(kāi)源的.NET多類(lèi)型文件解壓縮組件SharpZipLib(.NET組件介紹之七)
本篇文章主要介紹了免費(fèi)開(kāi)源的.NET多類(lèi)型文件解壓縮組件SharpZipLib,這也是一種解壓縮組件,具有一定的參考價(jià)值,有興趣的可以了解一下。2016-12-12.NET?6開(kāi)發(fā)TodoList應(yīng)用之實(shí)現(xiàn)DELETE請(qǐng)求與HTTP請(qǐng)求冪等性
這篇文章主要介紹了在.NET6開(kāi)發(fā)中如何實(shí)現(xiàn)DELETE請(qǐng)求以及HTTP請(qǐng)求冪等性的,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2021-12-12