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: }
运行结果如下:
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="184" height="104" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/05_3.jpg">
图3.2 Setter注入运行结果
3.1.2 构造注入
另外一种依赖注入方式,是通过客户类的构造函数,向客户类注入服务类实例。
构造注入(Constructor Injection)是指在客户类中,设置一个服务类接口类型的数据成员,并以构造函数为注入点,这个构造函数接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="566" height="311" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/06_3.jpg">
图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风格外观,而内部业务是一样的。
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="683" height="709" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/07_3.jpg">
图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 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风格,编译运行上述代码,运行结果如下:
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="208" height="119" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/08_3.jpg">
图3.5 配置Mac风格后的运行结果
现在,我们不动程序,仅仅将配置文件中的“Mac”改为Windows,运行后结果如下:
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="207" height="107" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/09_3.jpg">
图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”,而是通过类的名字来实例化对象,那么我们只要将类的名字作为配置项,就可以实现在不修改代码的情况下,加载未来才出现的类。所以,反射给了语言“预见未来”的能力,使得多态性和依赖注入的威力大增。
下面是引入反射机制后,对上面例子的改进:
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="487" height="670" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/10_3.jpg">
图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: }
反射不仅可以与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
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="275" height="88" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/11_3.png">
Spring.NET是Java平台上Spring对.NET平台的移植,使用方法和Spring很像,并且功能强大,是.NET平台上大中型开发IoC Container的首选之一。除了DI外,Spring.NET也包括AOP等诸多功能。
Spring.NET的官方网站是:http://www.springframework.net/
4.3.2 Unity
PHP?refimg=" + this.src)" style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" border="0" alt="" width="326" height="91" src="http://img.jb51.cc/vcimg/static/loading.png" src="http://images.cnblogs.com/cnblogs_com/leoo2sk/WindowsLiveWriter/a5703ca6e7d5_D180/12_3.gif">
对于小型项目和讲求敏捷的团队,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