1、什麼是依賴注入 框架
依賴注入的正式定義:ide
依賴注入(Dependency Injection),是這樣一個過程:因爲某客戶類只依賴於服務類的一個接口,而不依賴於具體服務類,因此客戶類只定義一個注入點。在程序運行過程當中,客戶類不直接實例化具體服務類實例,而是客戶類的運行上下文環境或專門組件負責實例化服務類,而後將其注入到客戶類中,保證客戶類的正常運行。函數
2、依賴注入的類別this
1.Setter注入
Setter注入(Setter Injection)是指在客戶類中,設置一個服務類接口類型的數據成員,並設置一個Set方法做爲注入點,這個Set方法接受一個具體的服務類實例爲參數,並將它賦給服務類接口類型的數據成員。spa
下面給出Setter注入的示例代碼。設計
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal interface IServiceClass { String ServiceInfo(); } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal class ServiceClassA : IServiceClass { public String ServiceInfo() { return "我是ServceClassA"; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal class ServiceClassB : IServiceClass { public String ServiceInfo() { return "我是ServceClassB"; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { internal class ClientClass { //注入點 private IServiceClass _serviceImpl; //客戶類中的方法,初始化注入點 public void Set_ServiceImpl(IServiceClass serviceImpl) { this._serviceImpl = serviceImpl; } public void ShowInfo() { Console.WriteLine(_serviceImpl.ServiceInfo()); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SetterInjection { class Program { static void Main(string[] args) { IServiceClass serviceA = new ServiceClassA(); IServiceClass serviceB = new ServiceClassB(); ClientClass client = new ClientClass(); client.Set_ServiceImpl(serviceA); client.ShowInfo();//結果:我是ServceClassA client.Set_ServiceImpl(serviceB); client.ShowInfo();//結果:我是ServceClassB Console.ReadLine(); } } }
運行結果以下:3d
2.構造注入code
另一種依賴注入方式,是經過客戶類的構造函數,向客戶類注入服務類實例。xml
構造注入(Constructor Injection)是指在客戶類中,設置一個服務類接口類型的數據成員,並以構造函數爲注入點,這個構造函數接受一個具體的服務類實例爲參數,並將它賦給服務類接口類型的數據成員。對象
與Setter注入很相似,只是注入點由Setter方法變成了構造方法。這裏要注意,因爲構造注入只能在實例化客戶類時注入一次,因此一點注入,程序運行期間是無法改變一個客戶類對象內的服務類實例的。
因爲構造注入和Setter注入的IServiceClass,ServiceClassA和ServiceClassB是同樣的,因此這裏給出另外ClientClass類的示例代碼。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConstructorInjection { internal class ClientClass { private IServiceClass _serviceImpl; public ClientClass(IServiceClass serviceImpl) { this._serviceImpl = serviceImpl; } public void ShowInfo() { Console.WriteLine(_serviceImpl.ServiceInfo()); } } }
能夠看到,惟一的變化就是構造函數取代了Set_ServiceImpl方法,成爲了注入點。
3. 依賴獲取
上面提到的注入方式,都是客戶類被動接受所依賴的服務類,這也符合「注入」這個詞。不過還有一種方法,能夠和依賴注入達到相同的目的,就是依賴獲取。
依賴獲取(Dependency Locate)是指在系統中提供一個獲取點,客戶類仍然依賴服務類的接口。當客戶類須要服務類時,從獲取點主動取得指定的服務類,具體的服務類類型由獲取點的配置決定。
能夠看到,這種方法變被動爲主動,使得客戶類在須要時主動獲取服務類,而將多態性的實現封裝到獲取點裏面。獲取點能夠有不少種實現,也許最容易想到的就是創建一個Simple Factory做爲獲取點,客戶類傳入一個指定字符串,以獲取相應服務類實例。若是所依賴的服務類是一系列類,那麼依賴獲取通常利用Abstract Factory模式構建獲取點,而後,將服務類多態性轉移到工廠的多態性上,而工廠的類型依賴一個外部配置,如XML文件。
不過,不論使用Simple Factory仍是Abstract Factory,都避免不了判斷服務類類型或工廠類型,這樣系統中總要有一個地方存在不符合OCP的if…else或switch…case結構,這種缺陷是Simple Factory和Abstract Factory以及依賴獲取自己沒法消除的,而在某些支持反射的語言中(如C#),經過將反射機制的引入完全解決了這個問題(後面討論)。
下面給一個具體的例子,如今咱們假設有個程序,既可使用Windows風格外觀,又可使用Mac風格外觀,而內部業務是同樣的。
上圖乍看有點複雜,不過若是讀者熟悉Abstract Factory模式,應該能很容易看懂,這就是Abstract Factory在實際中的一個應用。這裏的Factory Container做爲獲取點,是一個靜態類,它的「Type構造函數」依據外部的XML配置文件,決定實例化哪一個工廠。下面仍是來看示例代碼。因爲不一樣組件的代碼是類似的,這裏只給出Button組件的示例代碼,完整代碼請參考文末附上的完整源程序。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal interface IButton { String ShowInfo(); } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class WindowsButton : IButton { public String Description { get; private set; } public WindowsButton() { this.Description = "Windows風格按鈕"; } public String ShowInfo() { return this.Description; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class MacButton : IButton { public String Description { get; private set; } public MacButton() { this.Description = " Mac風格按鈕"; } public String ShowInfo() { return this.Description; } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal interface IFactory { IWindow MakeWindow(); IButton MakeButton(); ITextBox MakeTextBox(); } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class WindowsFactory : IFactory { public IWindow MakeWindow() { return new WindowsWindow(); } public IButton MakeButton() { return new WindowsButton(); } public ITextBox MakeTextBox() { return new WindowsTextBox(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { internal sealed class MacFactory : IFactory { public IWindow MakeWindow() { return new MacWindow(); } public IButton MakeButton() { return new MacButton(); } public ITextBox MakeTextBox() { return new MacTextBox(); } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml; namespace DependencyLocate { internal static class FactoryContainer { public static IFactory factory { get; private set; } static FactoryContainer() { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load("http://www.cnblogs.com/Config.xml"); XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0]; if ("Windows" == xmlNode.Value) { factory = new WindowsFactory(); } else if ("Mac" == xmlNode.Value) { factory = new MacFactory(); } else { throw new Exception("Factory Init Error"); } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DependencyLocate { class Program { static void Main(string[] args) { IFactory factory = FactoryContainer.factory; IWindow window = factory.MakeWindow(); Console.WriteLine("建立 " + window.ShowInfo()); IButton button = factory.MakeButton(); Console.WriteLine("建立 " + button.ShowInfo()); ITextBox textBox = factory.MakeTextBox(); Console.WriteLine("建立 " + textBox.ShowInfo()); Console.ReadLine(); } } }
這裏咱們用XML做爲配置文件。配置文件Config.xml以下:
<?xml version="1.0" encoding="utf-8" ?> <config> <factory>Mac</factory> </config>
能夠看到,這裏咱們將配置設置爲Mac風格,編譯運行上述代碼,運行結果以下:
配置Mac風格後的運行結果
如今,咱們不動程序,僅僅將配置文件中的「Mac」改成Windows,運行後結果以下:
配置爲Windows風格後的運行結果
從運行結果看出,咱們僅僅經過修改配置文件,就改變了整個程序的行爲(咱們甚至沒有從新編譯程序),這就是多態性的威力,也是依賴注入效果。
反射與依賴注入
回想上面Dependency Locate的例子,咱們雖然使用了多態性和Abstract Factory,但對OCP貫徹的不夠完全。在理解這點前,朋友們必定要注意潛在擴展在哪裏,潛在會出現擴展的地方是「新的組件系列」而不是「組件種類」,也就是說,這裏咱們假設組件就三種,不會增長新的組件,但可能出現新的外觀系列,如須要加一套Ubuntu風格的組件,咱們能夠新增UbuntuWindow、UbuntuButton、UbuntuTextBox和UbuntuFactory,並分別實現相應接口,這是符合OCP的,由於這是擴展。但咱們除了修改配置文件,還要無可避免的修改FactoryContainer,須要加一個分支條件,這個地方破壞了OCP。依賴注入自己是沒有能力解決這個問題的,但若是語言支持反射機制(Reflection),則這個問題就迎刃而解。
咱們想一想,如今的難點是出在這裏:對象最終仍是要經過「new」來實例化,而「new」只能實例化當前已有的類,若是將來有新類添加進來,必須修改代碼。若是,咱們能有一種方法,不是經過「new」,而是經過類的名字來實例化對象,那麼咱們只要將類的名字做爲配置項,就能夠實如今不修改代碼的狀況下,加載將來纔出現的類。因此,反射給了語言「預見將來」的能力,使得多態性和依賴注入的威力大增。
下面是引入反射機制後,對上面例子的改進:
能夠看出,引入反射機制後,結構簡單了不少,一個反射工廠代替了之前的一堆工廠,Factory Container也不須要了。並且之後有新組件系列加入時,反射工廠是不用改變的,只需改變配置文件就能夠完成。下面給出反射工廠和配置文件的代碼。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.Xml; namespace DependencyLocate { internal static class ReflectionFactory { private static String _windowType; private static String _buttonType; private static String _textBoxType; static ReflectionFactory() { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load("http://www.cnblogs.com/Config.xml"); XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0]; _windowType = xmlNode.ChildNodes[0].Value; _buttonType = xmlNode.ChildNodes[1].Value; _textBoxType = xmlNode.ChildNodes[2].Value; } public static IWindow MakeWindow() { return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _windowType) as IWindow; } public static IButton MakeButton() { return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _buttonType) as IButton; } public static ITextBox MakeTextBox() { return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _textBoxType) as ITextBox; } } }
配置文件以下:
<?xml version="1.0" encoding="utf-8" ?> <config> <window>MacWindow</window> <button>MacButton</button> <textBox>MacTextBox</textBox> </config>
反射不只能夠與Dependency Locate結合,也能夠與Setter Injection與Construtor Injection結合。反射機制的引入,下降了依賴注入結構的複雜度,使得依賴注入完全符合OCP,併爲通用依賴注入框架(如Spring.NET中的IoC部分、Unity等)的設計提供了可能性。