上一章咱們講了構造注入與設值注入,這一篇咱們主要講接口注入與特性注入。css
接口注入是將抽象類型的入口以方法定義在一個接口中,若是客戶類型須要得到這個方法,就須要以實現這個接口的方式完成注入。實際上接口注入有很強的侵入性,除了要求客戶類型增長前面兩種方式所須要的代碼外,還必須顯示地定義一個新的接口並要求客戶類型實現它。html
//定義須要注入ITimeProvider的類型 interface IobjectWithTimeProvider { ITimeProvider TimeProvider { get; set; } } //經過接口方式實現注入 public class Client:IobjectWithTimeProvider { public ITimeProvider TimeProvider { get; set; } }
[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#實現接口注入提供了「新生」。ide
/// <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; } } }
[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())); }
直觀上,客戶程序可能在使用上作出讓步以適應變化,但這違背了依賴注入的初衷,即三個角色(客戶對象、Assembler、抽象類型)之中兩個不能變,若是在Assembler和客戶類型選擇,爲了客戶對象影響最小,咱們只好在Assembler上下功夫,由於它的職責是負責組裝。反過來說,若是注入過程還須要修改客戶程序,那咱們就沒有必要去「削足適履」地去用「依賴注入」了。this
所以,爲了能經過特性方式完成依賴注入,咱們只好在Assembler上下功夫spa
(錯誤的實現狀況)設計
class SystemTimeAttribute:Attribute,ITimeProvider{…}code
[SystemTime]htm
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; } }
[TestMethod] public void Test1() { Assert.IsTrue(new Client().GetYear() > 0); }