一、什么是依賴注入
依賴注入的正式定義:
依賴注入(Dependency Injection),是這樣一個過程:由於某客戶類只依賴於服務類的一個接口,而不依賴於具體服務類,所以客戶類只定義一個注入點。在程序運行過程中,客戶類不直接實例化具體服務類實例,而是客戶類的運行上下文環境或專門組件負責實例化服務類,然后將其注入到客戶類中,保證客戶類的正常運行。
二、依賴注入的類別
1.Setter注入
Setter注入(Setter Injection)是指在客戶類中,設置一個服務類接口類型的數據成員,並設置一個Set方法作為注入點,這個Set方法接受一個具體的服務類實例為參數,並將它賦給服務類接口類型的數據成員。
下面給出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(); } } }
運行結果如下:

2.構造注入
另外一種依賴注入方式,是通過客戶類的構造函數,向客戶類注入服務類實例。
構造注入(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等)的設計提供了可能性。
