反转控制容器与依赖注入模式中英文翻译资料.doc

上传人:来看看 文档编号:3263157 上传时间:2019-08-06 格式:DOC 页数:27 大小:129.52KB
返回 下载 相关 举报
反转控制容器与依赖注入模式中英文翻译资料.doc_第1页
第1页 / 共27页
反转控制容器与依赖注入模式中英文翻译资料.doc_第2页
第2页 / 共27页
反转控制容器与依赖注入模式中英文翻译资料.doc_第3页
第3页 / 共27页
反转控制容器与依赖注入模式中英文翻译资料.doc_第4页
第4页 / 共27页
反转控制容器与依赖注入模式中英文翻译资料.doc_第5页
第5页 / 共27页
点击查看更多>>
资源描述

《反转控制容器与依赖注入模式中英文翻译资料.doc》由会员分享,可在线阅读,更多相关《反转控制容器与依赖注入模式中英文翻译资料.doc(27页珍藏版)》请在三一文库上搜索。

1、反转控制容器与依赖注入模式在企业级Java 的世界里存在一个有趣的现象:有很多人投入很多精力来研究主流J2EE 技术的替代品自然,这大多发生在open source 社群。在很大程度上,这可以看作是开发者对主流J2EE 技术的笨重和复杂作出的回应,但其中的确有很多极富创意的想法,的确提供了一些可供选择的方案。J2EE 开发者常遇到的一个问题就是如何组装不同的程序元素:如果web 控制器体系结构和数据库接口是由不同的团队所开发的,彼此几乎一无所知,你应该如何让它们配合工作?很多框架尝试过解决这个问题,有几个框架索性朝这个方向发展,提供了更通用的”组装各层组件”的方案。这样的框架通常被称为”轻量级

2、容器”,PicoContainer 和Spring 都在此列。在这些容器背后,一些有趣的设计原则发挥着作用。这些原则已经超越了特定容器的限制,甚至已经超越了Java 平台的范畴。在本文中,我就要初步揭示这些原则。我使用的范例是Java 代码,但正如我的大多数文章一样,这些原则也同样适用于其它的OO 环境,特别是.NET。组件和服务这样的话题立即将我拖进了一个棘手的术语问题:如何区分”服务”(service)和”组件”(component)?你可以毫不费力地找出关于这两个词定义的长篇大论。有鉴于此,对于这两个遭到了严重滥用的词汇,我将首先说明它们在本文中的用法。在本文中,”组建”指的是一个软件单

3、元,它将被作者无法控制的其他应用程序使用,但后者不能对组件进行修改。也就是说,使用一个组件的应用程序不能修改组件的源代码,但可以通过作者预留的某种途径对其进行扩展,以改变组件的行为。服务和组件有某种相似之处:它们都将被外部的应用程序使用。在我看来,两者之间最大的差异在于:组件是在本地使用的(例如JAR 文件、程序集、DLL、或者源码导入);而服务是要通过同步或异步的远程接口来远程使用的(例如web service、消息系统、RPC,或者socket)。在本文中,我将主要使用“服务”这个词,但文中的大多数逻辑也同样适用于本地组件。实际上,为了方便地访问远程服务,你往往需要某种本地组件框架。不过,

4、“组件或者服务”这样一个词组实在太麻烦了,而且“服务”这个词当下也很流行,所以本文将用”服务”指代这两者。一个简单的例子为了更好地说明问题,我要引入一个例子。和我以前用的所有例子一样,这是一个超级简单的例子:它非常小,小得有点不够真实,但足以帮助你看清其中的道理,而不至于陷入真实例子的泥潭中无法自拔。在这个例子中,我编写了一个组件,用于提供一份电影清单,清单上列出的影片都是由一位特定的导演执导的。实现这个伟大的功能只需要一个方法:class MovieLister. public Movie moviesDirectedBy(String arg) List allMovies = finde

5、r.findAll(); for (Iterator it = allMovies.iterator(); it.hasNext();) Movie movie = (Movie) it.next(); if (!movie.getDirector().equals(arg) it.remove(); return (Movie) allMovies.toArray(new MovieallMovies.size();这个功能的实现极其简单:moviesDirectedBy 方法首先请求finder(影片搜寻者)对象(我们稍后会谈到这个对象)返回后者所知道的所有影片,然后遍历finder 对象

6、返回的清单,并返回其中由特定的某个导演执导的影片。非常简单,不过不必担心,这只是整个例子的脚手架罢了。我们真正想要考察的是finder对象,或者说,如何将MovieLister对象与特定的finder对象连接起来。为什么我们对这个问题特别感兴趣?因为我希望上面这个漂亮的moviesDirectedBy方法完全不依赖于影片的实际存储方式。所以,这个方法只能引用一个finder对象,而finder对象则必须知道如何对findAll 方法作出回应。为了帮助读者更清楚地理解,我给finder定义了一个接口:public interface MovieFinder List findAll();现在,两

7、个对象之间基本没有耦合关系。但是,当我要实际寻找影片时,就必须涉及到MovieFinder 的某个具体子类。在这里,我把”涉及具体子类”的代码放在MovieLister 类的构造子中。class MovieLister. private MovieFinder finder; public MovieLister() finder = new ColonDelimitedMovieFinder(movies1.txt); 这个实现类的名字就说明:我将要从一个逗号分隔的文本文件中获得影片列表。你不必操心具体的实现细节,只要设想这样一个实现类就可以了。现在,如果这个类只由我自己使用,一切都没问题。

8、但是,如果我的朋友叹服于这个精彩的功能,也想使用我的程序,那又会怎么样呢?如果他们也把影片清单保存在一个逗号分隔的文本文件中,并且也把这个文件命名为” movie1.txt ”,那么一切还是没问题。如果他们只是给这个文件改改名,我也可以从一个配置文件获得文件名,这也很容易。但是,如果他们用完全不同的方式例如SQL 数据库、XML 文件、web service,或者另一种格式的文本文件来存储影片清单呢?在这种情况下,我们需要用另一个类来获取数据。由于已经定义了MovieFinder接口,我可以不用修改moviesDirectedBy 方法。但是,我仍然需要通过某种途径获得合适的MovieFind

9、er实现类的实例。图1:”在MovieLister 类中直接创建MovieFinder 实例”时的依赖关系Figure 1: The dependencies using a simple creation in the lister class图1 展现了这种情况下的依赖关系:MovieLister 类既依赖于MovieFinder接口,也依赖于具体的实现类。我们当然希望MovieLister 类只依赖于接口,但我们要如何获得一个MovieFinder子类的实例呢?在Patterns of Enterprise Application Architecture 一书中,我们把这种情况称为”插

10、件”(plugin):MovieFinder的实现类不是在编译期连入程序之中的,因为我并不知道我的朋友会使用哪个实现类。我们希望MovieLister 类能够与MovieFinder的任何实现类配合工作,并且允许在运行期插入具体的实现类,插入动作完全脱离我(原作者)的控制。这里的问题就是:如何设计这个连接过程,使MovieLister 类在不知道实现类细节的前提下与其实例协同工作。将这个例子推而广之,在一个真实的系统中,我们可能有数十个服务和组件。在任何时候,我们总可以对使用组件的情形加以抽象,通过接口与具体的组件交流(如果组件并没有设计一个接口,也可以通过适配器与之交流)。但是,如果我们希望

11、以不同的方式部署这个系统,就需要用插件机制来处理服务之间的交互过程,这样我们才可能在不同的部署方案中使用不同的实现。所以,现在的核心问题就是:如何将这些插件组合成一个应用程序?这正是新生的轻量级容器所面临的主要问题,而它们解决这个问题的手段无一例外地是控制反转(Inversion of Control)模式。控制反转几位轻量级容器的作者曾骄傲地对我说:这些容器非常有用,因为它们实现了”控制反转”。这样的说辞让我深感迷惑:控制反转是框架所共有的特征,如果仅仅因为使用了控制反转就认为这些轻量级容器与众不同,就好像在说”我的轿车是与众不同的,因为它有四个轮子”。问题的关键在于:它们反转了哪方面的控制

12、?我第一次接触到的控制反转针对的是用户界面的主控权。早期的用户界面是完全由应用程序来控制的,你预先设计一系列命令,例如”输入姓名”、”输入地址”等,应用程序逐条输出提示信息,并取回用户的响应。而在图形用户界面环境下,UI 框架将负责执行一个主循环,你的应用程序只需为屏幕的各个区域提供事件处理函数即可。在这里,程序的主控权发生了反转:从应用程序移到了UI框架。对于这些新生的容器,它们反转的是”如何定位插件的具体实现”。在前面那个简单的例子中, MovieLister 类负责定位MovieFinder 的具体实现它直接实例化后者的一个子类。这样一来,MovieFinder 也就不成其为一个插件了,

13、因为它并不是在运行期插入应用程序中的。而这些轻量级容器则使用了更为灵活的办法,只要插件遵循一定的规则,一个独立的组装模块就能够将插件的具体实现”注入”到应用程序中。因此,我想我们需要给这个模式起一个更能说明其特点的名字”控制反转”这个名字太宽泛了,常常让人有些迷惑。与多位IoC 爱好者讨论之后,我们决定将这个模式叫做”依赖注入”(Dependency Injection)。下面,我将开始介绍Dependency Injection 模式的几种不同形式。不过,在此之前,我要首先指出:要消除应用程序对插件实现的依赖,依赖注入并不是唯一的选择,你也可以用Service Locator 模式获得同样的

14、效果。介绍完Dependency Injection 模式之后,我也会谈到Service Locator 模式。依赖注入的几种形式Dependency Injection 模式的基本思想是:用一个单独的对象(装配器)来获得MovieFinder的一个合适的实现,并将其实例赋给MovieLister 类的一个字段。这样一来,我们就得到了图2所示的依赖图:图2:引入依赖注入器之后的依赖关系Figure 2: The dependencies for a Dependency Injector依赖注入的形式主要有三种,我分别将它们叫做构造子注入(Constructor Injection)、设值方法

15、注入(Setter Injection)和接口注入(Interface Injection)。如果读过最近关于IoC 的一些讨论材料,你不难看出:这三种注入形式分别就是type 1 IoC(接口注入)、type 2 IoC(设值方法注入)和type 3 IoC(构造子注入)。我发现数字编号往往比较难记,所以我使用了这里的命名方式。使用PicoContainer 进行构造子注入首先,我要向读者展示如何用一个名为PicoContainer 的轻量级容器完成依赖注入。之所以从这里开始,主要是因为我在ThoughtWorks 公司的几个同事在PicoContainer 的开发社群中非常活跃没错,也可以

16、说是某种偏袒吧。PicoContainer 通过构造子来判断”如何将MovieFinder 实例注入MovieLister 类”。因此,MovieLister 类必须声明一个构造子,并在其中包含所有需要注入的元素:class MovieLister. public MovieLister(MovieFinder finder) this.finder = finder; MovieFinder 实例本身也将由PicoContainer来管理,因此文本文件的名字也可以由容器注入:class ColonMovieFinder. public ColonMovieFinder(String file

17、name) this.filename = filename; 随后,需要告诉PicoContainer:各个接口分别与哪个实现类关联、将哪个字符串注入MovieFinder组件。private MutablePicoContainer configureContainer() MutablePicoContainer pico = new DefaultPicoContainer(); Parameter finderParams = new ConstantParameter(movies1.txt); pico.registerComponentImplementation(MovieF

18、inder.class, ColonMovieFinder.class, finderParams); pico.registerComponentImplementation(MovieLister.class); return pico; 这段配置代码通常位于另一个类。对于我们这个例子,使用我的MovieLister 类的朋友需要在自己的设置类中编写合适的配置代码。当然,还可以将这些配置信息放在一个单独的配置文件中,这也是一种常见的做法。你可以编写一个类来读取配置文件,然后对容器进行合适的设置。尽管PicoContainer 本身并不包含这项功能,但另一个与它关系紧密的项目NanoCont

19、ainer 提供了一些包装,允许开发者使用XML 配置文件保存配置信息。NanoContainer能够解析XML 文件,并对底下的PicoContainer 进行配置。这个项目的哲学观念就是:将配置文件的格式与底层的配置机制分离开。使用这个容器,你写出的代码大概会是这样:public void testWithPico() MutablePicoContainer pico = configureContainer(); MovieListerlister=(MovieLister)pico.getComponentInstance(MovieLister.class); Movie movi

20、es = lister.moviesDirectedBy(Sergio Leone); assertEquals(Once Upon a Time in the West, movies0.getTitle(); 尽管在这里我使用了构造子注入,实际上PicoContainer 也支持设值方法注入,不过该项目的开发者更推荐使用构造子注入。使用Spring 进行设值方法注入Spring 框架是一个用途广泛的企业级Java 开发框架,其中包括了针对事务、持久化框架、web应用开发和JDBC 等常用功能的抽象。和PicoContainer 一样,它也同时支持构造子注入和设值方法注入,但该项目的开发者更

21、推荐使用设值方法注入恰好适合这个例子。为了让MovieLister 类接受注入, 我需要为它定义一个设值方法, 该方法接受类型为MovieFinder的参数:class MovieLister. private MovieFinder finder; public void setFinder(MovieFinder finder) this.finder = finder; 类似地,在MovieFinder的实现类中,我也文件名属性定义了一个设值方法:class ColonMovieFinder. public void setFilename(String filename) this.f

22、ilename = filename; 第三步是设定配置文件。Spring 支持多种配置方式,你可以通过XML 文件进行配置,也可以直接在代码中配置。不过,XML 文件是比较理想的配置方式。 movies1.txt 于是,测试代码大概就像下面这样:public void testWithSpring() throws Exception ApplicationContext ctx = new FileSystemXmlApplicationContext(spring.xml); MovieLister lister = (MovieLister) ctx.getBean(MovieList

23、er); Movie movies = lister.moviesDirectedBy(Sergio Leone); assertEquals(Once Upon a Time in the West, movies0.getTitle(); 接口注入除了前面两种注入技术,还可以在接口中定义需要注入的信息,并通过接口完成注入。Avalon框架就使用了类似的技术。在这里,我首先用简单的范例代码说明它的用法,后面还会有更深入的讨论。首先,我需要定义一个接口,组件的注入将通过这个接口进行。在本例中,这个接口的用途是将一个MovieFinder实例注入继承了该接口的对象。public interfac

24、e InjectFinder void injectFinder(MovieFinder finder);然后,我使用类似的方法将文件名注入MovieFinder的实现类:public interface InjectFinderFilename void injectFilename (String filename);class ColonMovieFinder implements MovieFinder, InjectFinderFilename. public void injectFilename(String filename) this.filename = filename;

25、现在,还需要用一些配置代码将所有的组件实现装配起来。简单起见,我直接在代码中完成配置,并将配置好的MovieLister 对象保存在名为lister的字段中:class Tester. private Container container; private void configureContainer() container = new Container(); registerComponents(); registerInjectors(); container.start(); 这个配置器有两个工作,以键值对的形式进行组件的注册,如同其它例子中做的一样。class Tester. p

26、rivate void registerComponents() container.registerComponent(MovieLister, MovieLister.class); container.registerComponent(MovieFinder, ColonMovieFinder.class); 结下来的步骤是注册用来注入依赖组件的”注入器”。每个注入的接口都需要一些注入代码。在这里,我将”注入器”注册到容器中。每个注入器实现一个注入器接口。class Tester. private void registerInjectors() container.registerI

27、njector(InjectFinder.class, container.lookup(MovieFinder); container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector(); public interface Injector public void inject(Object target);class ColonMovieFinder implements Injector. public void inject(Object target) (InjectFinder) tar

28、get).injectFinder(this); class Tester. public static class FinderFilenameInjector implements Injector public void inject(Object target) (InjectFinderFilename)target).injectFilename(movies1.txt); The tests then use the container.class IfaceTester. public void testIface() configureContainer(); MovieLi

29、ster lister = (MovieLister)container.lookup(MovieLister); Movie movies = lister.moviesDirectedBy(Sergio Leone); assertEquals(Once Upon a Time in the West, movies0.getTitle();容器使用了声明的结构注入来解除依赖,并通过注入器对现有依赖进行注入。(该容器的实现我就不给读者展示了,因为没有什么价值。)Inversion of Control Containers and the Dependency Injection patt

30、ernOne of the entertaining things about the enterprise Java world is the huge amount of activity in building alternatives to the mainstream J2EE technologies, much of it happening in open source. A lot of this is a reaction to the heavyweight complexity in the mainstream J2EE world, but much of it i

31、s also exploring alternatives and coming up with creative ideas. A common issue to deal with is how to wire together different elements: how do you fit together this web controller architecture with that database interface backing when they were built by different teams with little knowledge of each

32、 other.A number of frameworks have taken a stab at this problem, and several are branching out to provide a general capability to assemble components from different layers. These are often referred to as lightweight containers, examples include PicoContainer, and Spring.Underlying these containers a

33、re a number of interesting design principles, things that go beyond both these specific containers and indeed the Java platform. Here I want to start exploring some of these principles. The examples I use are in Java, but like most of my writing the principles are equally applicable to other OO envi

34、ronments, particularly .NET.Components and ServicesThe topic of wiring elements together drags me almost immediately into the knotty terminology problems that surround the terms service and component. You find long and contradictory articles on the definition of these things with ease. For my purpos

35、es here are my current uses of these overloaded terms.I use component to mean a glob of software thats intended to be used, without change, by application that is out of the control of the writers of the component. By without change I mean that the using application doesnt change the source code of

36、the components, although they may alter the components behavior by extending it in ways allowed by the component writers. A service is similar to a component in that its used by foreign applications. The main difference is that I expect a component to be used locally (think jar file, assembly, dll,

37、or a source import). A service will be used remotely through some remote interface, either synchronous or asynchronous (eg web service, messaging system, RPC, or socket.)I mostly use service in this article, but much of the same logic can be applied to local components too. Indeed often you need som

38、e kind of local component framework to easily access a remote service. But writing component or service is tiring to read and write, and services are much more fashionable at the moment.A Naive ExampleTo help make all of this more concrete Ill use a running example to talk about all of this. Like al

39、l of my examples its one of those super-simple examples; small enough to be unreal, but hopefully enough for you to visualize whats going on without falling into the bog of a real example.In this example Im writing a component that provides a list of movies directed by a particular director. This st

40、unningly useful function is implemented by a single method.class MovieLister. public Movie moviesDirectedBy(String arg) List allMovies = finder.findAll(); for (Iterator it = allMovies.iterator(); it.hasNext();) Movie movie = (Movie) it.next(); if (!movie.getDirector().equals(arg) it.remove(); return

41、 (Movie) allMovies.toArray(new MovieallMovies.size(); The implementation of this function is naive in the extreme, it asks a finder object (which well get to in a moment) to return every film it knows about. Then it just hunts through this list to return those directed by a particular director. This

42、 particular piece of naivety Im not going to fix, since its just the scaffolding for the real point of this article.The real point of this article is this finder object, or particularly how we connect the lister object with a particular finder object. The reason why this is interesting is that I wan

43、t my wonderful moviesDirectedBy method to be completely independent of how all the movies are being stored. So all the method does is refer to a finder, and all that finder does is know how to respond to the findAll method. I can bring this out by defining an interface for the finder.public interfac

44、e MovieFinder List findAll();Now all of this is very well decoupled, but at some point I have to come up with a concrete class to actually come up with the movies. In this case I put the code for this in the constructor of my lister class.class MovieLister. private MovieFinder finder; public MovieLi

45、ster() finder = new ColonDelimitedMovieFinder(movies1.txt); The name of the implementation class comes from the fact that Im getting my list from a colon delimited text file. Ill spare you the details, after all the point is just that theres some implementation.Now if Im using this class for just myself, this is all fine and dandy. But what happens when my friends are overwhelmed by a desire

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 研究报告 > 信息产业


经营许可证编号:宁ICP备18001539号-1