在前面的篇幅中對依賴倒置原則和IoC架構的使用隻是做了個簡單的介紹,并沒有很詳細的去示範,可能有的朋友還是區分不了依賴倒置、依賴注入、控制反轉這幾個名詞,或許知道的也隻是知道依賴倒置是原則,依賴注入、控制反轉都是實作的方式,我将在下面對這些個名詞做詳細的介紹,在篇幅的最後還會自己實作了IoC容器的功能。
.NET裡簡易實作IoC
前言
依賴倒置原則
我們先來看一段代碼,代碼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中的現實,而我們是不希望這種事情發生。
這個時候我們再看依賴原則的定義(度娘的):
A.高層次的子產品不應該依賴于低層次的子產品,他們都應該依賴于抽象。
B.抽象不應該依賴于具體,具體應該依賴于抽象。
A.高層次的子產品不應該依賴于低層次的子產品,他們都應該依賴于抽象
對于A,照着字面意思來說的話很簡單了,已經沒法辦再用文字來描述了,看代碼吧,
代碼1-2
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類型中也依賴于抽象了。
圖1
圖1中所示的就是代碼1-2所要表示的類型結構了,也就是依賴倒置原則中A的實作,從圖1中我們可以看到依賴倒置原則裡還裝着開放封閉原則,這裡所要說明的意思就是依賴倒置原則是開放封閉原則的基礎。
從圖1中的結構來看,如果是站在開放封閉原則的角度來看也是沒有問題的,對擴充開放對修改關閉,在需求變動的時候隻要重新實作個依賴于抽象的下層,利用多态則可實作對擴充開放。
如果是站在依賴倒置原則的角度去看,那就是符合了依賴倒置原則定義的A條。
(Ps:這裡有的朋友可能會說上面的示例中Top也依賴于具體了,我隻想說請注意你的人身安全,
我這個人脾氣不太好。
開個玩笑,對于Top也依賴于具體的事确實是有的,後面會有說明)
B.抽象不應該依賴于具體,具體應該依賴于抽象
對于依賴倒置原則定義的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
這一小節就來解決上述的問題,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架構當作學習的對象,當然感興趣的朋友還是可以自己寫的。
搬磚不易,且搬且用心,感謝各位工友的支援,謝謝大家。
作者:金源
出處:http://www.cnblogs.com/jin-yuan/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面