首页 > 学院 > 开发设计 > 正文

Java 控制反转和依赖注入模式【翻译】【整理】

2019-11-14 23:20:44
字体:
来源:转载
供稿:网友
java 控制反转和依赖注入模式【翻译】【整理】Inversion of Control Containers and the Dependency Injection pattern ——Martin Fowler 本文内容
  • Component and Service(组件和服务)
  • A Naive Example(一个超级简单的例子)
  • Inversion of Control(控制反转)
  • Forms of Dependency Injection(依赖注入的几种形式)
    • Constructor Injection with PicoContainer(用 PicoContainer 进行构造函数注入)
    • Setter Injection with SPRing(用 Spring 进行 Setter 方法注入)
    • Interface Injection(接口注入)
  • Using a Service Locator(使用服务定位器)
    • Using a Segregated Interface for the Locator(为定位器提供分离的接口)
    • A Dynamic Service Locator(动态服务定位器)
    • Using both a locator and injection with Avalon(用 Avalon 同时使用定位器和依赖注入)
  • Deciding which option to use(作出选择)
    • Service Locator vs Dependency Injection(服务定位器 vs.  依赖注入)
    • Constructor versus Setter Injection(构造函数注入 vs. 设值方法注入)
    • Code or configuration files(代码配置 vs. 配置文件)
    • Separating Configuration from Use(分离配置与使用)
  • Some further issues(更多的问题)
  • Concluding Thoughts(结论和思考)
  • 参考资料

看了几篇关于“依赖注入”的文章,无论是百度,还是 Wiki 等等,觉得原文说得是最清楚的~你可以下载我实现文中示例的 Demo~

这篇文章的另一个意义在于,如果你看不懂该文的英文,说明你应该补充文中的术语了,看看作者用什么词来描述“控制反转和依赖注入”~

下载 Demo 1(使用 PicoContainer 2.14.1) 下载 Demo 2(使用 PicoContainer 1.2 和 nanocontainer-1.1.2)

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 社群近来掀起了一阵轻量级容器的热潮,这些容器能够帮助开发者将来自不同项目的组件组装成为一个内聚的应用程序。在这些容器的背后是同一个模式,决定了这些容器进行组件装配的方式。人们称这个模式为“控制反转”( Inversion of Control,IoC)。本文我将深入探索这个模式的工作原理,给它一个更能描述其特点的名字——“依赖注入”(Dependency Injection),并将其与“服务定位器”(Service Locator)模式作一个比较。不过,它们之间的差异并不太重要,重要的是:将组件的配置与使用分离——而这都是两个模式的目标。


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 技术的替代品——大都发生在 open source 社群。在很大程度上,这可以看作是开发者对主流 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。 

Component and Service(组件和服务)

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.

“装配程序元素”的话题立即将我拖进了一个棘手的术语问题:如何区分“服务”(service)和“组件”(component)?你可以很容易地找到关于这两个词长篇大论、彼此矛盾的定义。鉴于此,对于这两个遭到了严重滥用的词,我将首先说明它们在本文中的用法。

I 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.

所谓“组件”是指这样一个软件单元:它将被作者无法控制的其他应用程序使用,而后者不能对组件进行修改,也就是说,使用这个组件的应用程序不能修改组件的源代码,但可以通过作者预留的某种途径对其进行扩展以改变组件的行为。

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 Service、消息系统、RPC,或者 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 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.

本文我将主要使用“服务”这个词,但文中的大多数逻辑也同样适用于本地组件。事实上,为了方便地访问远程服务,你往往需要某种本地组件框架。不过,“组件或者服务”这样一个词组实在太麻烦了,而且“服务”这个词当下也很流行,所以本文将用“服务”指代这两者。

A Naive Example(一个超级简单的例子)

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.

这个功能的实现极其简单,moviesDirectedBy 方法首先请求 finder(影片搜寻者)对象(我们稍后会谈到这个对象)以返回它所知道的所有影片,然后遍历 finder 对象,并返回特定导演执导的影片。我只给出一个简单的代码片段。

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.

我们真正想要考察的是 finder 对象,或者说,如何将 MovieLister 对象与特定的 finder 对象连接起来。为什么我们对这个问题特别感兴趣?因为,我希望上面的 moviesDirectedBy 方法完全不依赖于影片的实际存储方式。所以,所有的方法都会引用一个 finder 对象,而 finder 对象则必须知道如何回应 findAll 方法。为了帮助读者更清楚地理解,我给 finder 定义了一个接口:

public interface 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.

现在,这两个对象很好地解耦了。但当我要实际寻找影片时,就必须涉及到 MovieFinder 的某个具体子类。在这里,我把涉及具体子类的代码放在 MovieLister 类构造函数中。

class MovieLister...
 
  private MovieFinder finder;
  public MovieLister() {
    finder = new ColonDelimitedMovieFinder("movies.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.

ColonDelimitedMovieFinder 这个实现类的名字就说明,我将要从一个逗号分隔的文本文件中获得影片列表。我不会给出该类的具体细节,毕竟这很简单,你设想有这样一个实现就行。

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.

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

naive

 

Figure 1: The dependencies using a simple creation in the lister class

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 显示了这种情况下的依赖关系。MovieLister 类既依赖于 MovieFinder 接口,也依赖于该接口的具体的实现类。我们当然希望 MovieLister 类仅仅依赖于接口,但又如何获得一个 MovieFinder 子类的实例呢?

也就是说,MovieLister 类依赖于 MovieFinder  接口,还要在该类中实例化该接口,但我们如何做才能不在 MovieLister 类内实例化 MovieFinder 接口,让这个类仅仅依赖与 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.

在 Patterns Of Enterprise Application Architecture 一书中,我们把这种情况称为插件(Plugin)。MovieFinder 的实现类不是在编译阶段连入程序之中的,因为我并不知道我的朋友会使用哪个实现类。我们希望 MovieLister 类能够与 MovieFinder 的任何实现类工作,并且允许在运行时插入具体的实现类,插入动作完全脱离我(原作者)的控制。问题是,如何设计这个连接过程,使 MovieLister 类在不知道实现类细节的前提下与其实例协同工作。

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.

所以,现在的核心问题是,如何将这些插件装配到一个应用程序?这正是新生的轻量级容器所面临的一个主要问题,而它们解决这个问题的手段无一例外地是控制反转(Inversion Of Control)模式。

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.

问题的关键在于:它们反转了哪方面的控制?我第一次接触到的控制反转是针对用户界面的主控权。早期的用户界面完全是由应用程序来控制的,你预先设计一系列命令,例如输入姓名、输入地址等,应用程序逐条输出提示信息,并取回用户的响应。而在图形用户界面环境下,UI 框架将负责执行一个主循环,你的应用程序只需为屏幕的各个区域提供事件处理函数即可。在这里,程序的控制权发生了反转:从应用程序转移到了框架。

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.

对于这些新生的容器,它们反转的是如何查找插件的具体实现。在前面的例子中,MovieLister 类直接实例化 MovieFinder。这样,MovieFinder 也就不成其为插件了,因为它不是在运行时插入应用程序中的。而这些轻量级容器则使用了更为灵活的方法,只要插件遵循一定的规则,一个独立的组件模块就能够将插件的具体实现注入到应用程序中。

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.

因此,我想我们需要给这个模式起一个更能说明其特点的名字——“控制反转”,这个名字太宽泛了,常常让人有些迷惑。与多位 IoC 爱好者讨论后,我们决定将这个模式叫做“依赖注入”(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.

下面,我将开始介绍 Dependency Injection 的几种不同形式。不过,在此之前,我要先指出,要消除应用程序对插件实现的依赖,依赖注入并不是唯一的选择,你也可以用 Service Locator 模式获得同样的效果。介绍完 Dependency Injection 模式之后,我会谈到 Service Locator 模式。

Forms of Dependency Injection(依赖注入的几种形式)

The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2.

Dependency Injection 模式的基本思想是,用一个单独的对象(装配器)来获得 MovieFinder 的一个合适的实现,并将其实例赋给 MovieLister 类的一个字段。这样,我们就得到了图 2 所示的依赖图。

injector

Figure 2: The dependencies for a Dependency Injector

There are three main styles of dependency injection. The names I'm using for them are Constructor Injection, Setter Injection, and Interface Injection. If you read about this stuff in the current discussions about Inversion of Control you'll hear these referred to as type 1 IoC (interface injection), type 2 IoC (setter injection) and type 3 IoC (constructor injection). I find numeric names rather hard to remember, which is why I've used the names I have here.

依赖注入的形式主要有三种,我们分别将它们叫做构造函数注入(Constructor Injection)、设置方法注入(Setter Injection)和接口注入(Interface Injection)。如果读过最近关于 IoC 的一些讨论材料,你不难看出,这三种方法分别就是 type 1 IoC(接口注入)、type 2 IoC(设置方法注入)和 type 3 IoC(构造函数注入)。我发现数字编号往往比较难记,所以我使用了这里的命名方式。

Constructor Injection with PicoContainer(用 PicoContainer 进行构造函数注入)

I'll start with showing how this injection is done using a lightweight container called PicoContainer. I'm starting here primarily because several of my colleagues at ThoughtWorks are very active in the development of PicoContainer (yes, it's a sort of corporate nepotism.)

首先,我要展示如何用轻量级容器 PicoContainer 完成依赖注入。之所以从它开始,主要是因为我在 ThoughtWorks 公司的几个同事在 PicoContainer 的开发社区中非常活跃(没错,也可以说是某种偏袒吧)。

PicoContainer uses a constructor to decide how to inject a finder implementation into the lister class. For this to work, the movie lister class needs to declare a constructor that includes everything it needs injected.

PicoContainer 使用构造函数来决定如何将 MovieFinder 实例注入 MovieLister 类。因此,MovieLister 类必须声明一个包含所有需要注入的元素的构造函数。

也就是说,所有需要注入的元素,是构造函数的形式参数。

class MovieLister...
 
  public MovieLister(MovieFinder finder) {
      this.finder = finder;       
  }

The finder itself will also be managed by the pico container, and as such will have the filename of the text file injected into it by the container.

MovieFinder 实例本身也将由 PicoContainer 来管理,因此本文文件的名字也可以由容器注入:

class ColonMovieFinder...
 
  public ColonMovieFinder(String filename) {
      this.filename = filename;
  }

The pico container then needs to be told which implementation class to associate with each interface, and which string to inject into the finder.

然后,需要告诉 pico 容器每个接口分别与哪个实现类相关联,将什么字符串注入到 MovieFinder 组件。

private MutablePicoContainer configureContainer() {
上一篇:java框架篇---struts开发

下一篇:HBase入门

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表