在設計模式中,尤爲是結構型模式不少時候解決的就是對象間的依賴關係,變依賴具體爲依賴抽象。平時開發中若是發現客戶程序依賴某個或某類對象,咱們經常會對他們進行一次抽象,造成抽象的抽象類、接口,這樣客戶程序就能夠擺脫所依賴的具體類型。html
這個過程當中有個環節被忽略了------誰來選擇客戶程序須要的知足抽象類型的具體類型呢?經過後面的介紹你會發現不少時候建立型模式能夠比較優雅的解決這個問題,但另外一個問題出現了,若是您設計的不是具體的業務邏輯,而是公共庫或框架程序呢,這時候你是一個‘服務方’,不是你調用那些構造類型,而是它們把抽象類型傳給你,怎麼鬆散地把加工好的抽象類型傳遞給客戶程序就是另外一回事了。設計模式
這個情形也就是常說的「控制反轉」,IOC:Inverse of Control;框架程序與抽象類型的調用關係就像常說的好萊塢規則:Don’t call me,I’ll call you框架
客戶程序須要一個提供System.DateTime類型當前系統時間的對象,而後根據須要僅僅把其中的年份部分提取出來,所以最初的代碼以下:ide
public class TimeProvider { public DateTime CurrentDate { get { return DateTime.Now; } } } public class Client { public int GetYear() { TimeProvider timeprovider = new TimeProvider(); return timeprovider.CurrentDate.Year; } }函數
後來某種緣由,發現使用.NET Framework自帶的日期類型精度不夠,須要提供其餘來源的TimeProvider,確保在不一樣精度要求的功能模塊中使用不一樣的TimeProvider。這樣問題集中在TimeProvider的變化會影響客戶程序,但其實客戶程序僅須要抽象地使用其獲取當前時間的方法。爲此,增長一個抽象接口ITimeProvider,改造後的示例以下:this
public interface ITimeProvider { public DateTime CurrentDate { get ; } } public class TimeProvider:ITimeProvider { public DateTime CurrentDate { get { return DateTime.Now; } } } public class Client { public int GetYear() { ITimeProvider timeprovider = new TimeProvider(); return timeprovider.CurrentDate.Year; } }spa
這樣看上去客戶程序後續處理所有依賴於抽象的ITimeProvider就能夠了,那麼問題是否解決了呢?沒有,由於客戶程序還要知道SystemTimeProvider的存在。所以,須要增長一個對象,由它選擇某種方式把ITimeProvider實例傳遞給客戶程序,這個對象被稱爲Assembler.設計
對於依賴注入而言,Assembler的做用很關鍵,由於它解決了客戶程序(也就是注入類型)與待注入實體類型間的依賴關係,今後Client只須要依賴ITimeProvider和Assembler便可,它並不知道TimeProviderImpl的存在。code
Assembler的職責以下:htm
- 知道每一個具體的TimeProviderImpl的類型。
- 根據客戶程序的須要,將對象ITimeProvider反饋給客戶程序。
- 負責對TimeProviderImpl實例化。
下面是一個Assembler的示例實現:
public class Assembler { //保存「抽象類型/實體類型"對應關係的字典 static Dictionary<Type, Type> dictionary = new Dictionary<Type, Type>(); static Assembler() { //註冊抽象類型須要使用的實體類型 //實際配置信息能夠從外層機制得到,例如經過配置定義 dictionary.Add(typeof(ITimeProvider), typeof(SystemTimeProvider)); } /// <summary> /// 根據客戶程序須要的抽象類型選擇相應的實體類型,並返回類型的實例 /// </summary> /// <param name="type"></param> /// <returns>實體類型實例</returns> public object Create(Type type)//主要用於非泛型方式調用 { if ((type == null) || !dictionary.ContainsKey(type)) throw new NullReferenceException(); return Activator.CreateInstance(dictionary[type]); } /// <summary> /// /// </summary> /// <typeparam name="T">抽象類型(抽象類/接口/或者某種基類)</typeparam> /// <returns></returns> public T Create<T>()//主要用於泛型方式調用 { return (T)Create(typeof(T)); } } }
構造注入方式又稱「構造子注入」、「構造函數注入」,顧名思義,這種注入方式就是在構造函數的執行過程當中,經過Assembler或其它機制把抽象類型做爲參數傳遞給客戶類型。這種方式雖然相對其它方式有些粗糙,並且僅在構造過程當中經過「一錘子買賣」的方式設置好,但不少時候咱們設計上正好就須要這種「一次性」的注入方式。
其實現方式以下:
//在構造函數中注入 public class Client { ITimeProvider timerprovider; public Client(ITimeProvider timeProvider) { this.timerprovider = timeProvider; } }UnitTest [TestClass] public class TestClent { [TestMethod] public void TestMethod1() { ITimeProvider timeProvider = (new Assembler()).Create<ITimeProvider>(); Assert.IsNotNull(timeProvider);//確承認以正常得到抽象類型實例 Client client = new Client(timeProvider);//在構造函數中注入 } }
設值注入是經過屬性方法賦值的辦法實現的。相對於構造方式而言,設值注入給了客戶類型後續修改的機會,它比較適合於客戶類型實例存活時間較長的情景。
實現方式以下:
//經過Setter實現中注入 public class Client { public ITimeProvider TimeProvider { get; set; } }
[TestClass] public class TestClent { [TestMethod] public void TestMethod1() { ITimeProvider timeProvider = (new Assembler()).Create<ITimeProvider>(); Assert.IsNotNull(timeProvider);//確承認以正常得到抽象類型實例 Client client = timeProvider;//經過Setter實現注入 } }
從C#語言發展看,設置注入方式更」Lamada化「,使用時能夠根據現場環境須要動態裝配,所以在新項目中我更傾向於使用設置注入。這個例子更時髦的寫法以下: [TestClass] public class TestClent { [TestMethod] public void TestMethod1() { var clent = new Client() { TimeProvider = (new Assembler()).Create<ITimeProvider>() }; } }