In the Java community there's been a rush of lightweight containers that help to assemble components from different projects into a cohesive application. Underlying these containers is a common pattern to how they perform the wiring, a concept they refer under the very generic name of "Inversion of Control". In this article I dig into how this pattern works, under the more specific name of "Dependency Injection", and contrast it with the Service Locator alternative. The choice between them is less important than the principle of separating configuration from use.
在Java社区中,有一大批轻量级容器帮助将不同项目的组件组装成一个内聚的应用程序。这些容器的底层是一个常见的模式,它们是如何执行连接的,这一概念是在“控制反转”的非常通用的名称下引用的。在本文中,我将深入探讨这个模式是如何工作的,在“依赖注入”的更具体的名称下,与服务定位器替代方案进行对比。它们之间的选择不像分离配置的原则那样重要。
One 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 is 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 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
.
关于企业级Java世界的一件有趣的事情是,在构建主流J2EE技术的替代方案方面的大量活动,其中很多都是在开源领域发生的。这在很大程度上是对主流J2EE世界中的重量级复杂性的一种反应,但其中大部分也在探索替代方案,并提出创造性的想法。要处理的一个常见问题是如何将不同的元素连接在一起:如何将这个web控制器体系结构与数据库接口的支持组合在一起,当它们由不同的团队构建时,彼此之间并不了解。一些框架已经尝试了这个问题,有几个框架正在扩展,以提供从不同层组装组件的一般功能。这些通常被称为轻量级容器,示例包括PicoContainer和Spring。
Underlying these containers are 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 environments, particularly .NET.
这些容器的基础是一些有趣的设计原则,它们超越了这些特定的容器,甚至超越了Java平台。在这里,我想开始探索其中的一些原则。我使用的示例都是Java的,但是像我的大多数文章一样,这些原则同样适用于其他OO环境,特别是.net。
Components and Services(
组件和服务)
The 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 purposes here are my current uses of these overloaded terms.
将元素连接在一起的主题几乎立即将我带入了围绕术语服务和组件的复杂术语问题。
您可以轻松地找到关于这些事情的定义的长期和矛盾的文章。出于我的目的,我现在使用这些重载的术语。
use component to mean a glob of software that's intended to be used, without change, by an application that is out of the control of the writers of the component. By 'without change' I mean that the using application doesn't change the source code of the components, although they may alter the component's behavior by extending it in ways allowed by the component writers.
我使用组件来表示一种软件的glob,它的用途是不需要更改,它是由组件的作者控制的应用程序所使用的。通过
“不改变”,我的意思是使用应用程序不会改变组件的源代码,尽管它们可能会通过组件作者允许的方式扩展组件的行为
A service is similar to a component in that it's used by foreign applications. The main difference is that I expect a component to be used locally (think jar file, assembly, dll, 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.)
服务类似于其被外部应用程序使用的组件。
主要的区别是,我希望组件可以在本地使用(思考jar文件、程序集、dll或源导入)。服务将通过一些远程接口(如web服务、消息传递系统、RPC或套接字)远程使用。
I mostly use service in this article, but much of the same logic can be applied to local components too. Indeed often you need some 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.
我在本文中主要使用服务,但是大部分相同的逻辑也可以应用于本地组件。实际上,您通常需要某种本地组件框架来轻松访问远程服务。但是,写“组件或服务”很累,读和写都很累,而且现在的服务更时尚。
To help make all of this more concrete I'll use a running example to talk about all of this. Like all of my examples it's one of those super-simple examples; small enough to be unreal, but hopefully enough for you to visualize what's going on without falling into the bog of a real example.
In this example I'm writing a component that provides a list of movies directed by a particular director. This stunningly 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 (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
}
The implementation of this function is naive in the extreme, it asks a finder object (which we'll 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 particular piece of naivety I'm not going to fix, since it's 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 want 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 interface MovieFinder { List findAll();}
这个函数的实现在极端情况下是幼稚的,它要求一个finder对象(我们稍后会讲到)来返回它所知道的每一个影片。然后,它就会通过这个列表来返回那些由某个特定的导演指导的人。我不打算修复这个特殊的部分,因为它只是本文的真正意义上的脚手架。
本文的重点是finder对象,特别是我们如何将lister对象与特定的finder对象连接起来。
为什么这很有趣,我希望我的美妙moviesDirectedBy方法是完全独立于所有的电影是如何存储的。
所以所有的方法都是指向一个finder,finder所做的就是知道如何响应findAll方法。我可以通过为finder定义一个接口来实现这一点。
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.
现在所有的这些都是很好的解耦的,但是在某种程度上,我必须要拿出一个具体的类来真正地制作电影。
在这种情况下,我将代码放在lister类的构造函数中。
class MovieLister...
private MovieFinder finder; public MovieLister() { finder = new ColonDelimitedMovieFinder("movies1.txt"); }
The name of the implementation class comes from the fact that I'm getting my list from a colon delimited text file. I'll spare you the details, after all the point is just that there's some implementation.
实现类的名称来自于我从一个冒号分隔的文本文件中获取列表的事实。我就不讲细节了,毕竟这是一种实现。
Now if I'm using this class for just myself, this is all fine and dandy. But what happens when my friends are overwhelmed by a desire for this wonderful functionality and would like a copy of my program? If they also store their movie listings in a colon delimited text file called "movies1.txt" then everything is wonderful. If they have a different name for their movies file, then it's easy to put the name of the file in a properties file. But what if they have a completely different form of storing their movie listing: a SQL database, an XML file, a web service, or just another format of text file? In this case we need a different class to grab that data. Now because I've defined a MovieFinder interface, this won't alter my moviesDirectedBy method. But I still need to have some way to get an instance of the right finder implementation into place.
如果我用这个类来表示我自己,这都很好。但是当我的朋友们被这种美妙的功能所淹没,并且想要我的程序的拷贝时,会发生什么呢?如果他们还在一个名为“movies1”的冒号分隔文本文件中存储他们的电影清单。然后一切都很美好。如果他们的电影文件有不同的名称,那么很容易将文件的名称放在属性文件中。但是,如果它们拥有完全不同的存储电影清单的形式:一个SQL数据库、一个XML文件、一个web服务,或者只是另一种文本文件格式呢?在这种情况下,我们需要一个不同的类来获取这些数据。现在,因为我已经定义了一个MovieFinder接口,这不会改变我的moviesDirectedBy方法。但是我仍然需要一些方法来获得正确的finder实现的实例。
Figure 1
shows the dependencies for this situation. The
MovieLister
class is dependent on both the
MovieFinder
interface and upon the implementation. We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?
图1显示了这种情况的依赖性。电影lister类依赖于MovieFinder接口和实现。如果它仅仅依赖于接口,那么我们会更喜欢它,但是我们如何使一个实例与它一起工作呢?
In my book
P of EAA
, we described this situation as a
Plugin
. The implementation class for the finder isn't linked into the program at compile time, since I don't know what my friends are going to use. Instead we want my lister to work with any implementation, and for that implementation to be plugged in at some later point, out of my hands. The problem is how can I make that link so that my lister class is ignorant of the implementation class, but can still talk to an instance to do its work.
在我的书中,我们把这种情况描述为一个插件。在编译时,finder的实现类没有链接到程序,因为我不知道我的朋友将使用什么。相反,我们希望我的listerl能够与任何实现一起工作,并希望我的lister能够在以后的某个时刻插入到我的手中。问题是,我如何才能创建这个链接,以便我的lister类不知道实现类,但是仍然可以与实例进行对话来完成它的工作。
Expanding this into a real system, we might have dozens of such services and components. In each case we can abstract our use of these components by talking to them through an interface (and using an adapter if the component isn't designed with an interface in mind). But if we wish to deploy this system in different ways, we need to use plugins to handle the interaction with these services so we can use different implementations in different deployments.
So the core problem is how do we assemble these plugins into an application? This is one of the main problems that this new breed of lightweight containers face, and universally they all do it using Inversion of Control.
将其扩展到一个实际系统中,我们可能会有几十个这样的服务和组件。在每一种情况下,我们都可以通过接口通过与它们对话来抽象我们对这些组件的使用(如果组件不是在设计时考虑到接口,那么就使用适配器)。但是,如果我们希望以不同的方式部署这个系统,我们需要使用插件来处理与这些服务的交互,这样我们就可以在不同的部署中使用不同的实现。
核心问题是如何将这些插件组装到应用程序中?这是这种新型轻量级容器所面临的主要问题之一,而且它们都是通过控制反转来实现的。
When these containers talk about how they are so useful because they implement "Inversion of Control" I end up very puzzled.
Inversion of control
is a common characteristic of frameworks, so saying that these lightweight containers are special because they use inversion of control is like saying my car is special because it has wheels.
The question is: "what aspect of control are they inverting?" When I first ran into inversion of control, it was in the main control of a user interface. Early user interfaces were controlled by the application program. You would have a sequence of commands like "Enter name", "enter address"; your program would drive the prompts and pick up a response to each one. With graphical (or even screen based) UIs the UI framework would contain this main loop and your program instead provided event handlers for the various fields on the screen. The main control of the program was inverted, moved away from you to the framework.
For this new breed of containers the inversion is about how they lookup a plugin implementation. In my naive example the lister looked up the finder implementation by directly instantiating it. This stops the finder from being a plugin. The approach that these containers use is to ensure that any user of a plugin follows some convention that allows a separate assembler module to inject the implementation into the lister.
As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name
Dependency Injection
.
I'm going to start by talking about the various forms of dependency injection, but I'll point out now that that's not the only way of removing the dependency from the application class to the plugin implementation. The other pattern you can use to do this is Service Locator, and I'll discuss that after I'm done with explaining Dependency Injection.
当这些容器讨论它们是如何非常有用的时候,因为它们实现了“控制反转”,我最终非常困惑。控制反转是框架的一个共同特征,所以说这些轻量级容器是特殊的,因为它们使用控制反转就像说我的车是特殊的,因为它有轮子。
问题是:“他们将控制的哪一方面颠倒了?”“当我第一次遇到控制反转时,它是在用户界面的主控制中。早期用户界面由应用程序控制。您将拥有一系列命令,如“输入名称”、“输入地址”;您的程序将驱动提示并对每个提示进行响应。使用图形化(甚至基于屏幕)UI框架将包含这个主循环,而您的程序将为屏幕上的各个字段提供事件处理程序。程序的主控制被倒转了,从你转到框架。
对于这种新类型的容器,反转是关于它们如何查找插件的实现。在我的幼稚示例中,lister通过直接实例化查找器实现。这阻止了finder作为插件。这些容器使用的方法是确保插件的任何用户遵循一些约定,允许独立的组装模块将实现注入到lister中。
因此,我认为我们需要一个更具体的名称。控制反转太笼统了,因此人们感到困惑。因此,我们与国际奥委会的各种倡导者进行了大量讨论,确定了“依赖注入”的名称。
我将首先讨论依赖注入的各种形式,但我现在要指出的是,这不是从应用程序类中移除依赖项到插件实现的唯一方法。您可以使用的另一个模式是服务定位器,我将在完成解释依赖注入之后讨论这个问题。