依赖注入的那些事儿(下)[转载]

 

   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

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