依賴注入的那些事兒(下)[轉載]

 

   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類的示例代碼。

Code: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組件的示例代碼,完整代碼請參考文末附上的完整源程序。

Code:按鈕接口
   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: }

 

Code:Windows風格按鈕
   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: }

 

Code:Mac風格按鈕
   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: }

 

Code:工廠接口
   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: }

 

Code:Windows組件工廠
   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: }

 

Code:Mac組件工廠
   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: }

 

Code:獲取點
   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: }
Code:測試代碼
   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也不需要了。而且以後有新組件系列加入時,反射工廠是不用改變的,只需改變配置文件就可以完成。下面給出反射工廠和配置文件的代碼。

Code:反射工廠
   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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章