可以看到,唯一的变化就是构造函数取代了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组件的示例代码,完整代码请参考文末附上的完整源程序。
这里我们用XML作为配置文件。配置文件Config.xml如下:
<?xml version="1.0" encoding="utf-8" ?>
<config>
<factory>Mac</factory>
</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也不需要了。而且以后有新组件系列加入时,反射工厂是不用改变的,只需改变配置文件就可以完成。下面给出反射工厂和配置文件的代码。
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等)的设计提供了可能性。
3.3 多态的活性与依赖注入
3.3.1 多态性的活性
这一节我们讨论多态的活性及其与依赖注入类型选择间密切的关系。
首先说明,“多态的活性”这个术语是我个人定义的,因为我没有找到既有的概念名词可以表达我的意思,所以就自己造了一个词。这里,某多态的活性是指被此多态隔离的变化所发生变化的频繁程度,频繁程度越高,则活性越强,反之亦然。
上文说过,多态性可以隔离变化,但是,不同的变化,发生的频率是不一样的,这就使得多态的活性有所差别,这种差别影响了依赖注入的类型选择。
举例来说,本文最开始提到的武器多态性,其活性非常高,因为在那个程序中,Role在一次运行中可能更换多次武器。而现在我们假设Role也实现了多态性,这是很可能的,因为在游戏中,不同类型的角色(如暗夜精?灵、牛头人、矮人等)很多属性和业务是想通的,所以很可能通过一个IRole或AbstractRole抽象类实现多态性,不过,Role在实例化后(一般在用户登录成功后),是不会变化的,很少有游戏允许同一个玩家在运行中变换Role类型,所以Role应该是一但实例化,就不会变化,但如果再实例化一个(如另一个玩家登录),则可能就变化了。最后,还有一种多态性是活性非常低的,如我们熟悉的数据访问层多态性,即使我们实现了SQL Server、Oracle和Access等多种数据库的访问层,并实现了依赖注入,但几乎遇不到程序运行着就改数据库或短期内数据库频繁变动的情况。
以上不同的多态性,不但特征不同,其目的一般也不同,总结如下:
高活多态性——指在客户类实例运行期间,服务类可能会改变的多态性。
中活多态性——指在客户类实例化后,服务类不会改变,但同一时间内存在的不同实例可能拥有不同类型的服务类。
低活多态性——指在客户类实例化后,服务类不会改变,且同一时间内所有客户类都拥有相同类型的服务类。
以上三种多态性,比较好的例子就是上文提到的武器多态性(高活)、角色多态性(中活)和数据访问层多态性(低活)。另外,我们说一种多态性是空间稳定的,如果同一客户类在同一时间内的所有实例都依赖相同类型的服务类,反之则叫做空间不稳定多态性。我们说一种多态性是时间稳定的,如果一个客户类在实例化后,所以来的服务类不能再次更改,反之则叫做时间不稳定多态性。显然,高活多态性时间和空间均不稳定;中活多态性是时间稳定的,但空间不稳定;低活多态性时间空间均稳定。
3.3.2 不同活性多态的依赖注入选择
一般来说,高活多态性适合使用Setter注入。因为Setter注入最灵活,也是唯一允许在同一客户类实例运行期间更改服务类的注入方式。并且这种注入一般由上下文环境通过Setter的参数指定服务类类型,方便灵活,适合频繁变化的高活多态性。
对于中活多态性,则适合使用Constructor注入。因为Constructor注入也是由上下文环境通过Construtor的参数指定服务类类型,但一点客户类实例化后,就不能进行再次注入,保证了其时间稳定性。
而对于低活多态性,则适合使用Dependency Locate并配合文件配置进行依赖注入,或Setter、Constructor配合配置文件注入,因为依赖源来自文件,如果要更改服务类,则需要更改配置文件,一则确保了低活多态性的时间和空间稳定性,二是更改配置文件的方式方便于大规模服务类替换。(因为低活多态性一旦改变行为,往往规模很大,如替换整个数据访问层,如果使用Setter和Construtor传参,程序中需要改变的地方不计其数)
本质上,这种选择是因为不同的依赖注入类型有着不同的稳定性,大家可以细细体会“活性”、“稳定性”和“依赖注入类型”之间密切的关系。
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,人民邮电出版