依賴注入(IOC)二
上一章我們講了構造注入與設值注入,這一篇我們主要講介面注入與特性注入。
介面注入
介面注入是將抽象型別的入口以方法定義在一個介面中,如果客戶型別需要獲得這個方法,就需要以實現這個介面的方式完成注入。實際上介面注入有很強的侵入性,除了要求客戶型別增加前面兩種方式所需要的程式碼外,還必須顯示地定義一個新的介面並要求客戶型別實現它。
//定義需要注入ITimeProvider的型別 interface IobjectWithTimeProvider { ITimeProvider TimeProvider { get; set; } } //通過介面方式實現注入 public class Client:IobjectWithTimeProvider { public ITimeProvider TimeProvider { get; set; } }
Unit Test
[TestClass] public class TestClent { [TestMethod] public void TestMethod1() { ITimeProvider timeProvider = (new Assembler()).Create<ITimeProvider>(); Assert.IsNotNull(timeProvider);//確認可以正常獲得抽象型別例項 IObjectWithTimeProvider objectWithProvider = new Client(); objectWithProvider.TimeProvider = timeProvider;//通過介面方式注入 } }
隨著C#語言的發展,介面注入可以採用與設值注入方式相似的方式實現,而且看上去很“Lamada化”。因為不用真正去實現介面,而是通過泛型引數的方式實現,可以說泛型為C#實現介面注入提供了“新生”。
/// <summary> /// 通過泛型引數實現介面注入 /// </summary> /// <typeparam name="T">待注入的介面型別</typeparam> public class Client<T>:ITimeProvider where T:ITimeProvider { /// <summary> /// 與設值方式相似的注入入口 /// </summary> public T Provider { get; set; } /// <summary> /// 類似傳統介面注入的實現方式 /// </summary> public DateTime CurrentDate { get { return Provider.CurrentDate; } } }
Unit Test
[TestMethod]
public void Test()
{
var clietn = new Client<ITimeProvider>()
{
Provider = (new Assembler().Create<ITimeProvider>())
};
//驗證設定方式注入的內容
Assert.IsNotNull(clietn.Provider);
Assert.IsNotInstanceOfType(clietn.Provider, typeof(SystemTimeProvider));
//驗證注入的介面是否可用
Assert.IsNotInstanceOfType(clietn.Provider.CurrentDate, typeof(DateTime));
//驗證是否滿足傳統介面注入的要求
Assert.IsTrue(typeof(ITimeProvider).IsAssignableFrom(clietn.GetType()));
}
基於特性的注入方式(Attributer)
直觀上,客戶程式可能在使用上做出讓步以適應變化,但這違背了依賴注入的初衷,即三個角色(客戶物件、Assembler、抽象型別)之中兩個不能變,如果在Assembler和客戶型別選擇,為了客戶物件影響最小,我們只好在Assembler上下功夫,因為它的職責是負責組裝。反過來講,如果注入過程還需要修改客戶程式,那我們就沒有必要去“削足適履”地去用“依賴注入”了。 因此,為了能通過特性方式完成依賴注入,我們只好在Assembler上下功夫
(錯誤的實現情況) class SystemTimeAttribute:Attribute,ITimeProvider{…} [SystemTime] class Client{…} 相信讀者也發現了,這樣做雖然把客戶型別需要的ITimeProvider通過“貼標籤”的方式告訴它了,但事實上又把客戶程式與SystemTimeAttribute“綁”上了,他們緊密的耦合在一起了。參考上面的三個實現,當抽象型別與客戶物件耦合的時候我們就要用Assembler解耦。 當特性方式出現類似情況時,我們寫一個AtttibuteAssembler不就行了嗎? 還不行,設計上要把Attribute設計成一個通道,出於擴充套件和通用性的考慮,它本身要協助AtttibuteAssembler完成ITimeProvider的裝配,最好還可以同時裝載其他抽象型別來修飾客戶型別。
示例程式碼如下
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class DecoratorAttribute : Attribute
{
//實現客戶型別實際需要的抽象型別的實體型別例項,即待注入客戶型別的內容
public readonly object Injector;
readonly Type type;
public DecoratorAttribute(Type type)
{
if (type == null) throw new ArgumentNullException("type");
this.type = type;
Injector = (new Assembler()).Create(this.type);
}
//客戶型別需要的抽象物件型別
public Type Type { get { return this.type; } }
}
public static class AttributeHelper
{
public static T Injector<T>(object target) where T : class
{
if (target == null) throw new ArgumentNullException("target");
return (T)(((DecoratorAttribute[])
target.GetType().GetCustomAttributes(typeof(DecoratorAttribute), false))
.Where(x => x.Type == typeof(T)).FirstOrDefault()
.Injector);
}
}
[Decorator(typeof(ITimeProvider))]
//應用Attribute,定義需要將ITimeProvider通過它注入
class Client
{
public int GetYear()
{
//與其他注入不同的是,這裡使用ITimeProvider來自自己的Attribute
var porvider = AttributeHelper.Injector<ITimeProvider>(this);
return porvider.CurrentDate.Year;
}
}
Unit Test
[TestMethod]
public void Test1()
{
Assert.IsTrue(new Client().GetYear() > 0);
}