在前面的篇幅中對依賴倒置原則和IoC框架的使用只是作了個簡單的介紹,並無很詳細的去演示,可能有的朋友仍是區分不了依賴倒置、依賴注入、控制反轉這幾個名詞,或許知道的也只是知道依賴倒置是原則,依賴注入、控制反轉都是實現的方式,我將在下面對這些個名詞作詳細的介紹,在篇幅的最後還會本身實現了IoC容器的功能。c#
咱們先來看一段代碼,代碼1-1安全
public class Top { public void Execution() { Underly underly = new Underly(); underly.WriterLine(); } } public class Underly { public void WriterLine() { Console.WriteLine("這是底層類型的輸出"); } }
從代碼1-1中看到Top類型的Execution()方法中包含了對Underly的依賴,直接使用的New來實例化Underly類型,導致兩個類型之間的耦合是屬於強耦合類型,這樣作會致使在需求發生變化的時候對於底層類型也就是Underly的修改會牽動到Top中的現實,而咱們是不但願這種事情發生。框架
這個時候咱們再看依賴原則的定義(度孃的):ide
A.高層次的模塊不該該依賴於低層次的模塊,他們都應該依賴於抽象。函數
B.抽象不該該依賴於具體,具體應該依賴於抽象。學習
對於A,照着字面意思來講的話很簡單了,已經無法辦再用文字來描述了,看代碼吧,測試
代碼1-2this
public class Top { public void Execution() { IUnderly underly = new Underly(); underly.WriterLine(); } } public interface IUnderly { void WriterLine(); } public class Underly:IUnderly { public void WriterLine() { Console.WriteLine("這是底層類型的輸出"); } }
在代碼1-2中咱們對Underly進行了抽象,而且讓其依賴於抽象(也就是實現接口),而在Top類型中也依賴於抽象了。spa
圖1設計
圖1中所示的就是代碼1-2所要表示的類型結構了,也就是依賴倒置原則中A的實現,從圖1中咱們能夠看到依賴倒置原則裏還裝着開放封閉原則,這裏所要說明的意思就是依賴倒置原則是開放封閉原則的基礎。
從圖1中的結構來看,若是是站在開放封閉原則的角度來看也是沒有問題的,對擴展開放對修改關閉,在需求變更的時候只要從新實現個依賴於抽象的下層,利用多態則可實現對擴展開放。
若是是站在依賴倒置原則的角度去看,那就是符合了依賴倒置原則定義的A條。
(Ps:這裏有的朋友可能會說上面的示例中Top也依賴於具體了,我只想說請注意你的人身安全,我這我的脾氣不太好。
開個玩笑,對於Top也依賴於具體的事確實是有的,後面會有說明)
對於依賴倒置原則定義的B來講,我分兩個部分來給你們解釋一下。
第一個部分就是抽象不該該依賴於具體, 咱們仍是經過代碼來講明吧。
代碼1-3
public interface IUnderly { void WriterLine(); IWriter CreateWriterInstance(); } public class Underly:IUnderly { public void WriterLine() { CreateWriterInstance().WriterLine(); } public IWriter CreateWriterInstance() { return new Writer(); } } public interface IWriter { void WriterLine(); } public class Writer : IWriter { public void WriterLine() { Console.WriteLine("這只是一個輸出"); } }
首先咱們新定義了一種輸出方式Writer和它的抽象IWriter接口類型,咱們想把它應用到Underly類型的輸出中,而後咱們修改了Underly的抽象類型也就是在IUnderly接口類型中新添加了一個CreateWriterInstance()方法,而且這個方法的返回類型是Writer的抽象,這樣就對應了依賴倒置原則定義中B條的前半句話:抽象不該該依賴於具體。
錯誤的示範,代碼1-4
public interface IUnderly { void WriterLine(); Writer CreateWriterInstance(); }
這裏這樣的壞處不少後果也很嚴重,就不去細說了慢慢體會一下應該會感受獲得。
從依賴倒置原則定義中B條的前半句話中來看,咱們能夠在碩大的.NET Framework中看一下一些抽象的定義中是否有依賴於具體的,應該是沒有至少我是沒發現。
對於B條定義的後半句話,也就是第二個部分:具體應該依賴於抽象,這部分的內容就是約束咱們在實際運用設計原則的時候會出現的問題,就比如上面的Top類型依然是依賴於具體了。
對於怎麼解決這樣的一個問題,有的朋友可能已經想到了,對的那就是依賴注入,都說依賴注入是依賴倒置原則的實現方式之一是不許確的,依賴注入解決的問題是將具體到具體的依賴轉換成具體到抽象的依賴。我是這麼認爲的,純屬我的觀點。
(ps:這是一種治標不治本的方法,DI把對象耦合的問題拋到了外部,也就是這樣才致使了IoC的誕生,後面會有介紹。)
圖2
對於上節中的示例中對象所依賴的圖示。爲了能像圖1中所示的結構那樣以及符合依賴倒置原則的定義,咱們將使用依賴注入的方式,先暫時性的解決這樣的問題。
依賴注入有三種方式,意思都差很少都是講外部抽象的引用設置到內部來從而實現注入。
這裏就簡單示例一下,
代碼1-5
public class Top { private IUnderly _Underly; public Top(IUnderly underly) { _Underly = underly; } public void Execution() { _Underly.WriterLine(); } } public interface IUnderly { void WriterLine(); } public class Underly:IUnderly { public void WriterLine() { Console.WriteLine("這只是一個底層類型的輸出"); } } class Program { static void Main(string[] args) { Top top = new Top(new Underly()); top.Execution(); Console.ReadLine(); } }
如代碼1-5所示那樣,在Top類型的構造函數中定義了下層類型的抽象做爲參數,以此達到依賴注入的目的。結果如圖3。
圖3
代碼1-6
public class Top { private IUnderly _Underly; public Top() { } public Top(IUnderly underly) { _Underly = underly; } public IUnderly Underly { get { return _Underly; } set { _Underly = value; } } public void Execution() { _Underly.WriterLine(); } } class Program { static void Main(string[] args) { Top top = new Top(); top.Underly = new Underly(); top.Execution(); Console.ReadLine(); } }
經過在內部設置屬性來獲取到底層抽象的引用,結果如圖3.
代碼1-7
public interface IQuote { void SetQuote(IUnderly underly); } public class Top:IQuote { private IUnderly _Underly; public Top() { } public Top(IUnderly underly) { _Underly = underly; } public IUnderly Underly { get { return _Underly; } set { _Underly = value; } } public void Execution() { _Underly.WriterLine(); } public void SetQuote(IUnderly underly) { _Underly = underly; } } class Program { static void Main(string[] args) { Top top = new Top(); top.SetQuote(new Underly()); top.Execution(); Console.ReadLine(); } }
接口注入的方式原理仍是同樣的,讓Top實現定義了設置引用方法的接口,依然是將外部的底層抽象引用設置到內部來,結果仍是同樣如圖3.
這樣雖然說沒什麼問題了,但也只是局部的沒有問題,咱們看一下上面三個示例中Program類型中的測試代碼,
圖4
繞了一圈依賴注入是把耦合的問題拋到了外部,拋到了要使用Top類型的對象中,這個問題就很嚴重了,咱們怎麼解決呢?不要緊經過IoC容器來實現。
這一小節就來解決上述的問題,Ioc又叫控制反轉,控制就是執行過程上的控制,反轉是往哪轉呢?
圖5
從圖5中咱們能夠看到,客戶端調用IoC容器,而且在IoC容器中執行依賴注入操做,最後返回上層對象交給客戶端,因此控制反轉是由在客戶端的控制權交由IoC容器,在IoC容器中進行依賴注入的操做後返回已達到控制權反轉的目的,歷來消弱對象間的耦合程度。
那麼IoC容器要作哪些工做呢?
圖6
核心功能:生成依賴注入過程當中的上層對象
基礎流程:
1.須要向IoC容器中註冊依賴注入過程當中抽象、具體。
2.在使用IoC的時候需向IoC中註冊上層對象的類型。
3.解析上層對象類型,而且執行生成對象操做
4.返回上層對象實例
功能對象定義:
1.抽象、具體關係維護的對象,用以維護依賴注入過程當中抽象、具體的對應關係。
2.解析對象類型的對象,根據依賴注入的幾種方式分析對象類型的構造和公共屬性而且生成,(公共屬性是符合IoC框架中定義的標準)。
3.公共屬性標準對象,用以通知IoC框架上層對象中哪些公共屬性須要被注入。
4.執行過程對象,用以表示框架執行流程,框架入口點。
初步就這樣定了,有可能下面定義的類型中上面沒有定義到,可是不妨礙,知道基礎流程就好了。那如今就開始吧。
首先咱們要定義IoC框架入口點,
代碼1-8
namespace FrameWork.IoC.Achieve.IoCAbstractBasics { public interface IIoCKernel { IIoCKernel Bind<T>(); IIoCKernel To<U>() where U : class; V GetValue<V>() where V : class; } }
對於IIoCKernel類型的定義,Bind和To兩個方法用於綁定抽象、具體到關係維護的對象中,而GetValue()方法則是用以獲取上層對象的實例,對於這種入口點的使用方式我是模仿的Ninject框架,會在最後的示例中演示怎麼使用。(由於我只用過這一個,仍是個半吊子只是簡單的用過)
下面咱們就來實現一下IIoCKernel,示例代碼1-9.
代碼1-9
using FrameWork.IoC.Achieve.IoCAbstractBasics; namespace FrameWork.IoC.Achieve.IoCBasics { public class IoCKernel : IIoCKernel { private Type _BaseType; public IoCKernel() { IoCContext.Context.DITyoeInfoManage = new DITypeInfoManage(); } public IIoCKernel Bind<T>() { _BaseType = typeof(T); return this; } public IIoCKernel To<U>() where U : class { Type achieveType = typeof(U); if (achieveType.BaseType == _BaseType||achieveType.GetInterface(_BaseType.Name)!=null) { IoCContext.Context.DITyoeInfoManage.AddTypeInfo(_BaseType, achieveType); } return this; } public V GetValue<V>() where V : class { return IoCContext.Context.DITypeAnalyticalProvider.CreteDITypeAnalaytical().GetValue<V>(); } } }
在代碼1-9中,IoCKernel實現了IIoCKernel接口,首先在其構造函數中,咱們對抽象、具體關係維護的對象進行了初始化,而且設置到了當前IoC框架的上下文中,咱們這裏先停一下,看一下抽象、具體關係維護對象的構造,示例代碼1-10.
代碼1-10
namespace FrameWork.IoC.Achieve.IoCBasics { /// <summary> /// DI類型關係信息管理 /// </summary> public class DITypeInfoManage { private Dictionary<Type, Type> _DITypeInfo; public DITypeInfoManage() { _DITypeInfo = new Dictionary<Type, Type>(); } /// <summary> /// 添加DI類型關係 /// </summary> /// <param name="key">抽象類型</param> /// <param name="value">實現類型</param> public void AddTypeInfo(Type key, Type value) { if (key == null) { throw new ArgumentNullException("key"); } if (_DITypeInfo.ContainsKey(key)) { return; } if (value == null) { throw new ArgumentNullException("value"); } _DITypeInfo.Add(key, value); } /// <summary> /// 獲取DI類型關係的實現類型 /// </summary> /// <param name="key">抽象類型</param> /// <returns></returns> public Type GetTypeInfo(Type key) { if (key == null) { throw new ArgumentNullException("key"); } if (_DITypeInfo.ContainsKey(key)) { return _DITypeInfo[key]; } return null; } public bool ContainsKey(Type key) { if (key == null) { throw new ArgumentNullException("key"); } return _DITypeInfo.ContainsKey(key); } } }
DITypeInfoManage類型對象表示着抽象、具體類型關係的信息維護,實則就是在內部封裝了鍵值隊,這裏就很少說了,而後咱們再看一下代碼1-9中IoC框架入口點類型的構造函數中初始化DITypeInfoManage類型設置的上下文對象,來看示例代碼1-11.
using FrameWork.IoC.Achieve.IoCAbstractBasics; using FrameWork.IoC.Achieve.Providers; using FrameWork.IoC.Achieve.IoCBasics; namespace FrameWork.IoC.Achieve { public class IoCContext { private IoCContext() { } private static IoCContext _Context; public static IoCContext Context { get { if (_Context == null) { _Context = new IoCContext(); } return _Context; } } private IDITypeAnalyticalProvider _DITypeAnalyticalProvider; public IDITypeAnalyticalProvider DITypeAnalyticalProvider { get { if (_DITypeAnalyticalProvider == null) { _DITypeAnalyticalProvider = new DefualtDITypeAnalyticalProivder(); } return _DITypeAnalyticalProvider; } set { _DITypeAnalyticalProvider = value; } } private DITypeInfoManage _DITypeInfoManage; public DITypeInfoManage DITyoeInfoManage { get { return _DITypeInfoManage; } set { _DITypeInfoManage = value; } } } }
代碼1-11中的定義的IoCContext說是上下文對象,說是這麼說,用以維護框架中必要的信息,實則就是一個單例模式的對象,可是意義上它仍是上下文對象,在這個對象裏面維護着所要依賴注入的抽象、具體類型維護的對象,這個對象咱們上面代碼1-10看過了,還有一個就是分析上層類型的提供程序對象,分析上層類型的提供程序對象是用以生成分析上層類型對象的,這樣作便於對外擴展,咱們就這樣順着往下看,看一下分析上層類型的提供程序對象,示例代碼1-12。
代碼1-12
using FrameWork.IoC.Achieve.IoCAbstractBasics; namespace FrameWork.IoC.Achieve.Providers { public interface IDITypeAnalyticalProvider { IDITypeAnalytical CreteDITypeAnalaytical(); } }
這裏的IDITypeAnalytical接口類型就是分析類型的抽象,在提供程序抽象中用以它來作返回類型,這也遵循着依賴倒置原則B條的抽象不依賴於具體。如今咱們來看一下默認實現,示例代碼1-13.
代碼1-13
using FrameWork.IoC.Achieve.IoCAbstractBasics; using FrameWork.IoC.Achieve.IoCBasics; namespace FrameWork.IoC.Achieve.Providers { public class DefualtDITypeAnalyticalProivder:IDITypeAnalyticalProvider { public IDITypeAnalytical CreteDITypeAnalaytical() { return new DITypeAnalytical(); } } }
在代碼1-13中定義的就是默認的分析上層類型提供程序了,默認返回的就是咱們框架中默認的分析上層類型對象,如今咱們就來看一下分析上層類型對象的抽象和具體實現,示例代碼1-14。
代碼1-14
namespace FrameWork.IoC.Achieve.IoCAbstractBasics { public interface IDITypeAnalytical { T GetValue<T>(); } }
只是定義了一個泛型的GetValue()方法,泛型類型固然就是所須要執行依賴注入而且生成的上層對象類型了,這裏沒什麼好說的,直接來看分析上層類型的具體實現吧,示例代碼1-15.
代碼1-15
using FrameWork.IoC.Achieve.IoCAbstractBasics; using System.Reflection; namespace FrameWork.IoC.Achieve.IoCBasics { public class DITypeAnalytical : IDITypeAnalytical { public T GetValue<T>() { Type type = typeof(T); return (T)TypeAnalytical(type); } private object TypeAnalytical(Type type) { ConstructorInfo[] constructorInfos = type.GetConstructors(); object instance = null; #region 構造函數注入 foreach (ConstructorInfo conInfo in constructorInfos) { if (conInfo.GetParameters().Length > 0) { ParameterInfo[] paras = conInfo.GetParameters(); List<object> args = new List<object>(); foreach (ParameterInfo para in paras) { if (IoCContext.Context.DITyoeInfoManage.ContainsKey(para.ParameterType)) { object par = TypeAnalytical(IoCContext.Context.DITyoeInfoManage.GetTypeInfo(para.ParameterType)); args.Add(par); } } instance = CreateInstance(type, args.ToArray()); break; } } #endregion if (instance == null) { instance = CreateInstance(type); } #region 屬性注入 if (type.GetProperties().Length > 0) { PropertyInfo[] proertyInfos = type.GetProperties(); foreach (PropertyInfo propertyInfo in proertyInfos) { if (propertyInfo.GetCustomAttributes(typeof(DITypeAttribute), false).Length > 0) { if (IoCContext.Context.DITyoeInfoManage.ContainsKey(propertyInfo.PropertyType)) { object propertyvalue = TypeAnalytical(IoCContext.Context.DITyoeInfoManage.GetTypeInfo(propertyInfo.PropertyType)); propertyInfo.SetValue(instance, propertyvalue, null); } } } } #endregion return instance; } private object CreateInstance(Type type,params object[] args) { return Activator.CreateInstance(type, args); } } }
在代碼1-15的定義中,主要的核心功能在TypeAnalytical()方法中,這裏主要說明一下這個方法的執行過程,首先是根據方法參數傳入的類型,這個類型就是要實現依賴注入的類型,爲何不說這個參數類型是上層類型?
是由於在首先執行的過程當中傳入的是上層類型,而後判斷其類型的構造函數,讀取構造函數的參數類型根據【抽象、具體類型的維護對象】來查找當前上層類型是否須要進行構造函數依賴,若是【抽象、具體類型的維護對象】中存在所需的類型,則對上層類型的構造函數參數類型進行實例建立,而且再次調用TypeAnalytical()方法,由於咱們不能肯定上層類型構造函數的參數類型是否須要進行依賴注入,因此這裏是遞歸的。
在建立完上層類型構造函數的參數類型實例後,便會對上層類型進行實例建立,由於這是依賴注入中構造函數注入的一種方式。
在此完畢後判斷TypeAnalytical()方法中instance實例是否爲空,若是是空的則說明上層類型沒有采起構造函數注入的方式,在此咱們仍是要建立它的實例,以便下面的進行屬性注入時對實例屬性的賦值。
以後咱們會對上層類型的全部公共屬性根據條件進行查找,查找符合咱們定義標準的公共屬性,也就是DITypeAttribute類型,這個類型下面會貼出示例代碼,假使在找到須要依賴注入的公共屬性後執行過程便和上面執行構造函數注入的方式相同。
(ps:這裏功能的定義並非很嚴謹,並且只針對了構造函數注入和屬性注入兩種方式,並無對接口注入提供支持。)
下面咱們看一下上面所說的屬性注入的特性類定義(也就是框架定義的規範),示例代碼1-16.
代碼1-16
namespace FrameWork.IoC.Achieve.IoCBasics { [AttributeUsage(AttributeTargets.Property,AllowMultiple=false,Inherited=false)] public class DITypeAttribute:Attribute { public DITypeAttribute() { } } }
就是一個簡單的特性類定義,用做規範約束。
最後咱們看一下測試用例:
代碼1-17
using FrameWork.IoC.Achieve.IoCBasics; using FrameWork.IoC.Achieve.IoCAbstractBasics; using FrameWork.IoC.Case; using FrameWork.IoC.Case.Test.TestOne; using FrameWork.IoC.Case.Test.TestTwo; namespace FrameWork.IoC.Case.Test { public class DITest { private IAbstractOne _AbstractOne; public DITest(IAbstractOne abstractone) { _AbstractOne = abstractone; } private IAbstractTwo _AbstractTwo; [DIType] public IAbstractTwo AbstractTwo { get { return _AbstractTwo; } set { _AbstractTwo = value; } } public void Writer(string meg) { _AbstractOne.WriterLine(meg); _AbstractTwo.WriterLine(meg); } } }
代碼1-17定義中對DITest分別進行了構造函數、屬性注入,注入類型分別對應着IAbstractOne、IAbstractTwo。咱們先來看一下IAbstractOne抽象、具體的定義,示例代碼1-18
代碼1-18
namespace FrameWork.IoC.Case.Test.TestOne { public interface IAbstractOne { void WriterLine(string meg); } public class AchieveOne:IAbstractOne { private IAbstractOne_One _AbstractOne_One; public AchieveOne(IAbstractOne_One abstractone) { _AbstractOne_One = abstractone; } private IAbstractOne_Two _AbstractOne_Two; [DIType] public IAbstractOne_Two AbstractOne_Two { get { return _AbstractOne_Two; } set { _AbstractOne_Two = value; } } public void WriterLine(string meg) { _AbstractOne_One.WirterLine(meg); _AbstractOne_Two.WriterLine(meg); Console.WriteLine(meg + "-This is TestOne"); } } }
代碼1-18中定義了IAbstractOne抽象、AchieveOne具體實現,而且在AchieveOne具體實現中還對IAbstractOne_One、IAbstractOne_Two分別進行了構造函數、屬性注入。從最上層來看就是嵌套的注入,這樣更能體現出IoC框架的重要性。
咱們看一下IAbstractOne_One、IAbstractOne_Two類型的抽象、具體定義,示例代碼1-19.
代碼1-19
namespace FrameWork.IoC.Case.Test.TestOne { public interface IAbstractOne_One { void WirterLine(string meg); } public class AbstractOne_One:IAbstractOne_One { public void WirterLine(string meg) { Console.WriteLine(meg + "-This is TestOne_One"); } } public interface IAbstractOne_Two { void WriterLine(string meg); } public class AbstractOne_Two:IAbstractOne_Two { public void WriterLine(string meg) { Console.WriteLine(meg + "-This is TestOne_Two"); } } }
最後咱們再看一下IAbstractTwo抽象和具體實現的定義,示例代碼1-20.
代碼1-20
namespace FrameWork.IoC.Case.Test.TestTwo { public interface IAbstractTwo { void WriterLine(string meg); } public class AchieveTwo:IAbstractTwo { public void WriterLine(string meg) { Console.WriteLine(meg + "-This is TestTwo"); } } }
真的是最後咱們看一下客戶端的調用代碼,示例代碼1-21,
代碼1-21
using FrameWork.IoC.Achieve.IoCBasics; using FrameWork.IoC.Achieve.IoCAbstractBasics; using FrameWork.IoC.Case; using FrameWork.IoC.Case.Test; using FrameWork.IoC.Case.Test.TestOne; using FrameWork.IoC.Case.Test.TestTwo; namespace FrameWork.IoC.Case { class Program { static void Main(string[] args) { #region IoCTest IIoCKernel iocKernel = new IoCKernel(); iocKernel.Bind<IAbstractOne>().To<AchieveOne>(); iocKernel.Bind<IAbstractTwo>().To<AchieveTwo>(); iocKernel.Bind<IAbstractOne_One>().To<AbstractOne_One>(); iocKernel.Bind<IAbstractOne_Two>().To<AbstractOne_Two>(); DITest diType = iocKernel.GetValue<DITest>(); diType.Writer("IoCFrameWorkTest"); #endregion Console.ReadLine(); } } }
最後看一下結果,如圖7
圖7
到這裏本篇的內容就結束了,自定義IoC只是一個參考,並無對IoC框架進行深刻的實現,只是以此作一個引導,建議你們仍是選擇一款合適的IoC框架看成學習的對象,固然感興趣的朋友仍是能夠本身寫的。
搬磚不易,且搬且用心,感謝各位工友的支持,謝謝你們。