依賴注入(IOC)


背景介紹

在設計模式中,尤其是結構型模式很多時候解決的就是對象間的依賴關系,變依賴具體為依賴抽象。平時開發中如果發現客戶程序依賴某個或某類對象,我們常常會對他們進行一次抽象,形成抽象的抽象類、接口,這樣客戶程序就可以擺脫所依賴的具體類型。

這個過程中有個環節被忽略了------誰來選擇客戶程序需要的滿足抽象類型的具體類型呢?通過后面的介紹你會發現很多時候創建型模式可以比較優雅的解決這個問題,但另一個問題出現了,如果您設計的不是具體的業務邏輯,而是公共庫或框架程序呢,這時候你是一個‘服務方’,不是你調用那些構造類型,而是它們把抽象類型傳給你,怎么松散地把加工好的抽象類型傳遞給客戶程序就是另一回事了。

這個情形也就是常說的“控制反轉”,IOC:Inverse of Control;框架程序與抽象類型的調用關系就像常說的好萊塢規則:Don’t call me,I’ll  call you

示例場景

 

       客戶程序需要一個提供System.DateTime類型當前系統時間的對象,然后根據需要僅僅把其中的年份部分提取出來,因此最初的代碼如下:

 

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,改造后的示例如下:

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; } }

這樣看上去客戶程序后續處理全部依賴於抽象的ITimeProvider就可以了,那么問題是否解決了呢?沒有,因為客戶程序還要知道SystemTimeProvider的存在。因此,需要增加一個對象,由它選擇某種方式把ITimeProvider實例傳遞給客戶程序,這個對象被稱為Assembler.

對於依賴注入而言,Assembler的作用很關鍵,因為它解決了客戶程序(也就是注入類型)與待注入實體類型間的依賴關系,從此Client只需要依賴ITimeProvider和Assembler即可,它並不知道TimeProviderImpl的存在。

Assembler的職責如下:

  • 知道每個具體的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)); } 
    }
}
    

 

 

 

構造注入(Constructor)

構造注入方式又稱“構造子注入”、“構造函數注入”,顧名思義,這種注入方式就是在構造函數的執行過程中,通過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)

設值注入是通過屬性方法賦值的辦法實現的。相對於構造方式而言,設值注入給了客戶類型后續修改的機會,它比較適合於客戶類型實例存活時間較長的情景。

實現方式如下:

//通過Setter實現中注入 public class Client { public ITimeProvider TimeProvider { get; set; } }

Unit Test

[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>() }; } }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM