1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace SetterInjection
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: IServiceClass serviceA = new ServiceClassA();
13: IServiceClass serviceB = new ServiceClassB();
14: ClientClass client = new ClientClass();
15:
16: client.Set_ServiceImpl(serviceA);
17: client.ShowInfo();
18: client.Set_ServiceImpl(serviceB);
19: client.ShowInfo();
20: }
21: }
22: }
運行結果如下:
圖3.2 Setter注入運行結果
3.1.2 構造注入
另外一種依賴注入方式,是通過客戶類的構造函數,向客戶類注入服務類實例。
構造注入(Constructor Injection)是指在客戶類中,設置一個服務類接口類型的數據成員,並以構造函數爲注入點,這個構造函數接受一個具體的服務類實例爲參數,並將它賦給服務類接口類型的數據成員。
圖3.3 構造注入示意
圖3.3是構造注入的示意圖,可以看出,與Setter注入很類似,只是注入點由Setter方法變成了構造方法。這裏要注意,由於構造注入只能在實例化客戶類時注入一次,所以一點注入,程序運行期間是沒法改變一個客戶類對象內的服務類實例的。
由於構造注入和Setter注入的IServiceClass,ServiceClassA和ServiceClassB是一樣的,所以這裏給出另外ClientClass類的示例代碼。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace ConstructorInjection
7: {
8: internal class ClientClass
9: {
10: private IServiceClass _serviceImpl;
11:
12: public ClientClass(IServiceClass serviceImpl)
13: {
14: this._serviceImpl = serviceImpl;
15: }
16:
17: public void ShowInfo()
18: {
19: Console.WriteLine(_serviceImpl.ServiceInfo());
20: }
21: }
22: }
可以看到,唯一的變化就是構造函數取代了Set_ServiceImpl方法,成爲了注入點。
3.1.3 依賴獲取
上面提到的注入方式,都是客戶類被動接受所依賴的服務類,這也符合“注入”這個詞。不過還有一種方法,可以和依賴注入達到相同的目的,就是依賴獲取。
依賴獲取(Dependency Locate)是指在系統中提供一個獲取點,客戶類仍然依賴服務類的接口。當客戶類需要服務類時,從獲取點主動取得指定的服務類,具體的服務類類型由獲取點的配置決定。
可以看到,這種方法變被動爲主動,使得客戶類在需要時主動獲取服務類,而將多態性的實現封裝到獲取點裏面。獲取點可以有很多種實現,也許最容易想到的就是建立一個Simple Factory作爲獲取點,客戶類傳入一個指定字符串,以獲取相應服務類實例。如果所依賴的服務類是一系列類,那麼依賴獲取一般利用Abstract Factory模式構建獲取點,然後,將服務類多態性轉移到工廠的多態性上,而工廠的類型依賴一個外部配置,如XML文件。
不過,不論使用Simple Factory還是Abstract Factory,都避免不了判斷服務類類型或工廠類型,這樣系統中總要有一個地方存在不符合OCP的if…else或switch…case結構,這種缺陷是Simple Factory和Abstract Factory以及依賴獲取本身無法消除的,而在某些支持反射的語言中(如C#),通過將反射機制的引入徹底解決了這個問題(後面討論)。
下面給一個具體的例子,現在我們假設有個程序,既可以使用Windows風格外觀,又可以使用Mac風格外觀,而內部業務是一樣的。
圖3.4 依賴獲取示意
上圖乍看有點複雜,不過如果讀者熟悉Abstract Factory模式,應該能很容易看懂,這就是Abstract Factory在實際中的一個應用。這裏的Factory Container作爲獲取點,是一個靜態類,它的“Type構造函數”依據外部的XML配置文件,決定實例化哪個工廠。下面還是來看示例代碼。由於不同組件的代碼是相似的,這裏只給出Button組件的示例代碼,完整代碼請參考文末附上的完整源程序。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace DependencyLocate
7: {
8: internal interface IButton
9: {
10: String ShowInfo();
11: }
12: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace DependencyLocate
7: {
8: internal sealed class WindowsButton : IButton
9: {
10: public String Description { get; private set; }
11:
12: public WindowsButton()
13: {
14: this.Description = "Windows風格按鈕";
15: }
16:
17: public String ShowInfo()
18: {
19: return this.Description;
20: }
21: }
22: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace DependencyLocate
7: {
8: internal sealed class MacButton : IButton
9: {
10: public String Description { get; private set; }
11:
12: public MacButton()
13: {
14: this.Description = " Mac風格按鈕";
15: }
16:
17: public String ShowInfo()
18: {
19: return this.Description;
20: }
21: }
22: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace DependencyLocate
7: {
8: internal interface IFactory
9: {
10: IWindow MakeWindow();
11:
12: IButton MakeButton();
13:
14: ITextBox MakeTextBox();
15: }
16: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace DependencyLocate
7: {
8: internal sealed class WindowsFactory : IFactory
9: {
10: public IWindow MakeWindow()
11: {
12: return new WindowsWindow();
13: }
14:
15: public IButton MakeButton()
16: {
17: return new WindowsButton();
18: }
19:
20: public ITextBox MakeTextBox()
21: {
22: return new WindowsTextBox();
23: }
24: }
25: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace DependencyLocate
7: {
8: internal sealed class MacFactory : IFactory
9: {
10: public IWindow MakeWindow()
11: {
12: return new MacWindow();
13: }
14:
15: public IButton MakeButton()
16: {
17: return new MacButton();
18: }
19:
20: public ITextBox MakeTextBox()
21: {
22: return new MacTextBox();
23: }
24: }
25: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Xml;
6:
7: namespace DependencyLocate
8: {
9: internal static class FactoryContainer
10: {
11: public static IFactory factory { get; private set; }
12:
13: static FactoryContainer()
14: {
15: XmlDocument xmlDoc = new XmlDocument();
16: xmlDoc.Load("http://www.cnblogs.com/Config.xml");
17: XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0].ChildNodes[0];
18:
19: if ("Windows" == xmlNode.Value)
20: {
21: factory = new WindowsFactory();
22: }
23: else if ("Mac" == xmlNode.Value)
24: {
25: factory = new MacFactory();
26: }
27: else
28: {
29: throw new Exception("Factory Init Error");
30: }
31: }
32: }
33: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace DependencyLocate
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: IFactory factory = FactoryContainer.factory;
13: IWindow window = factory.MakeWindow();
14: Console.WriteLine("創建 " + window.ShowInfo());
15: IButton button = factory.MakeButton();
16: Console.WriteLine("創建 " + button.ShowInfo());
17: ITextBox textBox = factory.MakeTextBox();
18: Console.WriteLine("創建 " + textBox.ShowInfo());
19:
20: Console.ReadLine();
21: }
22: }
23: }
這裏我們用XML作爲配置文件。配置文件Config.xml如下:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <config>
3: <factory>Mac</factory>
4: </config>
可以看到,這裏我們將配置設置爲Mac風格,編譯運行上述代碼,運行結果如下:
圖3.5 配置Mac風格後的運行結果
現在,我們不動程序,僅僅將配置文件中的“Mac”改爲Windows,運行後結果如下:
圖3.6 配置爲Windows風格後的運行結果
從運行結果看出,我們僅僅通過修改配置文件,就改變了整個程序的行爲(我們甚至沒有重新編譯程序),這就是多態性的威力,也是依賴注入效果。
本節共討論了三種基本的依賴注入類別,有關更多依賴注入類別和不同類別對比的知識,可以參考Martin Fowler的《Inversion of Control Containers and the Dependency Injection pattern》。
3.2 反射與依賴注入
回想上面Dependency Locate的例子,我們雖然使用了多態性和Abstract Factory,但對OCP貫徹的不夠徹底。在理解這點前,朋友們一定要注意潛在擴展在哪裏,潛在會出現擴展的地方是“新的組件系列”而不是“組件種類”,也就是說,這裏我們假設組件就三種,不會增加新的組件,但可能出現新的外觀系列,如需要加一套Ubuntu風格的組件,我們可以新增UbuntuWindow、UbuntuButton、UbuntuTextBox和UbuntuFactory,並分別實現相應接口,這是符合OCP的,因爲這是擴展。但我們除了修改配置文件,還要無可避免的修改FactoryContainer,需要加一個分支條件,這個地方破壞了OCP。依賴注入本身是沒有能力解決這個問題的,但如果語言支持反射機制(Reflection),則這個問題就迎刃而解。
我們想想,現在的難點是出在這裏:對象最終還是要通過“new”來實例化,而“new”只能實例化當前已有的類,如果未來有新類添加進來,必須修改代碼。如果,我們能有一種方法,不是通過“new”,而是通過類的名字來實例化對象,那麼我們只要將類的名字作爲配置項,就可以實現在不修改代碼的情況下,加載未來纔出現的類。所以,反射給了語言“預見未來”的能力,使得多態性和依賴注入的威力大增。
下面是引入反射機制後,對上面例子的改進:
圖3.7 引入反射機制的Dependency Locate
可以看出,引入反射機制後,結構簡單了很多,一個反射工廠代替了以前的一堆工廠,Factory Container也不需要了。而且以後有新組件系列加入時,反射工廠是不用改變的,只需改變配置文件就可以完成。下面給出反射工廠和配置文件的代碼。
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Reflection;
6: using System.Xml;
7:
8: namespace DependencyLocate
9: {
10: internal static class ReflectionFactory
11: {
12: private static String _windowType;
13: private static String _buttonType;
14: private static String _textBoxType;
15:
16: static ReflectionFactory()
17: {
18: XmlDocument xmlDoc = new XmlDocument();
19: xmlDoc.Load("http://www.cnblogs.com/Config.xml");
20: XmlNode xmlNode = xmlDoc.ChildNodes[1].ChildNodes[0];
21:
22: _windowType = xmlNode.ChildNodes[0].Value;
23: _buttonType = xmlNode.ChildNodes[1].Value;
24: _textBoxType = xmlNode.ChildNodes[2].Value;
25: }
26:
27: public static IWindow MakeWindow()
28: {
29: return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _windowType) as IWindow;
30: }
31:
32: public static IButton MakeButton()
33: {
34: return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _buttonType) as IButton;
35: }
36:
37: public static ITextBox MakeTextBox()
38: {
39: return Assembly.Load("DependencyLocate").CreateInstance("DependencyLocate." + _textBoxType) as ITextBox;
40: }
41: }
42: }
1: <?xml version="1.0" encoding="utf-8" ?>
2: <config>
3: <window>MacWindow</window>
4: <button>MacButton</button>
5: <textBox>MacTextBox</textBox>
6: </config>
反射不僅可以與Dependency Locate結合,也可以與Setter Injection與Construtor Injection結合。反射機制的引入,降低了依賴注入結構的複雜度,使得依賴注入徹底符合OCP,併爲通用依賴注入框架(如Spring.NET中的IoC部分、Unity等)的設計提供了可能性。
4 IoC Container
4.1 IoC Container出現的必然性
上面討論了諸多依賴注入的話題。說道依賴注入,就不能不說IoC Container(IoC容器),那麼到底什麼是IoC容器?我們還是先來看看它的出現背景。
我們知道,軟件開發領域有句著名的論斷:不要重複發明輪子!因爲軟件開發講求複用,所以,對於應用頻繁的需求,總是有人設計各種通用框架和類庫以減輕人們的開發負擔。例如,數據持久化是非常頻繁的需求,於是各種ORM框架應運而生;再如,對MVC的需求催生了Struts等一批用來實現MVC的框架。
隨着面向對象分析與設計的發展和成熟,OOA&D被越來越廣泛應用於各種項目中,然而,我們知道,用OO就不可能不用多態性,用多態性就不可能不用依賴注入,所以,依賴注入變成了非常頻繁的需求,而如果全部手工完成,不但負擔太重,而且還容易出錯。再加上反射機制的發明,於是,自然有人開始設計開發各種用於依賴注入的專用框架。這些專門用於實現依賴注入功能的組件或框架,就是IoC Container。
從這點看,IoC Container的出現有其歷史必然性。目前,最著名的IoC也許就是Java平臺上的Spring框架的IoC組件,而.NET平臺上也有Spring.NET和Unity等。
4.2 IoC Container的分類
前面曾經討論了三種依賴注入方式,但是,想通過方式對IoC Container進行分類很困難,因爲現在IoC Container都設計很完善,幾乎支持所有依賴注入方式。不過,根據不同框架的特性和慣用法,還是可以講IoC Container分爲兩個大類。
4.2.1 重量級IoC Container
所謂重量級IoC Container,是指一般用外部配置文件(一般是XML)作爲依賴源,並託管整個系統各個類的實例化的IoC Container。這種IoC Container,一般是承接了整個系統幾乎所有多態性的依賴注入工作,並承接了所有服務類的實例化工作,而且這些實例化依賴於一個外部配置文件,這種IoC Container,很像通過一個文件,定義整個系統多態結構,視野宏大,想要很好駕馭這種IoC Container,需要一定的架構設計能力和豐富的實踐經驗。
Spring和Spring.NET是重量級IoC Container的例子。一般來說,這種IoC Container穩定性有餘而活性不足,適合進行低活多態性的依賴注入。
4.2.2 輕量級IoC Container
還有一種IoC Container,一般不依賴外部配置文件,而主要使用傳參的Setter或Construtor注入,這種IoC Container叫做輕量級IoC Container。這種框架很靈活,使用方便,但往往不穩定,而且依賴點都是程序中的字符串參數,所以,不適合需要大規模替換和相對穩定的低活多態性,而對於高活多態性,有很好的效果。
Unity是一個典型的輕量級IoC Container。
4.3 .NET平臺上典型IoC Container推介
4.3.1 Spring.NET
Spring.NET是Java平臺上Spring對.NET平臺的移植,使用方法和Spring很像,並且功能強大,是.NET平臺上大中型開發IoC Container的首選之一。除了DI外,Spring.NET也包括AOP等諸多功能。
Spring.NET的官方網站是:http://www.springframework.net/
4.3.2 Unity
對於小型項目和講求敏捷的團隊,Spring.NET可能有點太重量級,那麼可以選擇輕量級的Unity。Unity是微軟patterns & practices團隊推出的輕量級框架,非常好用,目前最新版本是1.2。
Unity的官方網站是:http://unity.codeplex.com/
參考文獻
[1] Shivprasad koirala, Design pattern – Inversion of control and Dependency injection, http://www.codeproject.com/KB/aspnet/IOCDI.aspx
[2] Martin Fowler, Inversion of Control Containers and the Dependency Injection pattern, http://www.martinfowler.com/articles/injection.html
[3] Paul, IoC Types, http://docs.codehaus.org/display/PICO/IoC+Types
[4] Eric Freeman, Elisabeth Freeman. Head First Design Patterns. O’Reilly Media, 2004. ISBN 0596007142
[5] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995. ISBN 0201633612
[6] Patrick Smacchia 著,施凡等 譯,C#和.NET2.0 平臺、語言與框架。2008.1,人民郵電出版
[7] Jeffrey Rechter 著,CLR via C#(影印版)。2008.8,人民郵電出版
原文地址:http://www.cnblogs.com/leoo2sk/archive/2009/06/17/1504693.html