[置顶] 控制容器的反转和依赖注入模式

在Java社区是一个匆匆的轻量级容器,帮助来自不同项目的组件组装成一个有凝聚力的应用程序。这些容器的基础,是一种常见的模式,他们的表现如何布线,他们的概念是指“控制反转”下的通用名称。在这篇文章中,我挖掘到这种模式是如何工作的,“依赖注入”根据更具体的名称,并对比了服务定位器替代。它们之间的选择是比配置使用分离的原则,不那么重要。

2004年1月23日

马丁·福勒

翻译: 中国  葡萄牙  法国  意大利  罗马尼亚
标签: 流行   ·  设计   ·的 对象协同设计   · 应用程序架构

内容

  • 组件和服务
  • 一个天真的示例
  • 控制反转
  • 依赖注入的形式
    • PicoContainer的构造器注入
    • Setter注入与Spring
    • 接口注入
  • 使用服务定位器
    • 使用定位器隔离接口
    • 动态的服务定位器
    • 同时使用定位器和注射阿瓦隆
  • 决定使用哪个选项
    • 服务定位与依赖注入
    • 构造与Setter注入
    • 代码或配置文件
    • 使用分离配置
  • 一些进一步的问题
  • 总结思考

关于企业Java世界的有趣的事情之一是建设主流J2EE技术的替代品的大量活动,其中大部分是发生在开放源码。这是很多在主流的J2EE世界重量级复杂的反应,但它也正在探索替代品,并有创意的想法。一个常见的问题,要处理的是如何连接在一起不同的元素:你是怎么结合在一起的网络控制器架构与数据库接口的后盾,当他们建造小知识每个other.A框架由不同的团队已经采取了刺这个问题,和几个分支提供了一个通用的能力,从不同层次组件组装。这些通常被称为“轻量级容器,例子包括PicoContainer的,和弹簧

相关这些容器是一些有趣的设计原则,超越这些特定的容器,确实是Java平台的东西。在这里,我想开始探索一些这些原则。我使用的例子是在Java中,但最喜欢我的写作原则同样适用于其他面向对象的环境中,特别是NET。

组件和服务

布线元素的主题,一拖再拖,我几乎立刻陷入棘手的术语问题,围绕服务和组件。你发现这些东西的定义,轻松长,矛盾的文章。对于我而言这里是我目前使用这些重载条款。

我使用的组件,意味着拟用,没有改变,由控制组件的作家是一个应用程序,软件水珠。“没有改变”我的意思是,不改变使用的应用程序组件的源代码,虽然他们可能通过扩展组件编写者所允许的方式改变组件的行为。

服务是类似的一个组成部分,它的国外申请。主要的区别是,我期望在本地使用一个组件(认为jar文件,装配,DLL,或源进口)。一个服务可以远程使用,通过一些远程接口,同步或异步(例如Web服务,消息系统,RPC或插座。)

在这篇文章中我大多使用服务,但许多相同的逻辑可应用于本地组件。事实上,你经常需要某种本地组件框架,可以方便地访问远程服务。但写“组件或服务”是很累人的阅读和书写,服务更时髦的时刻。

一个天真的示例

为了帮助这更具体的,我将使用一个运行的例子谈论这一切。我的例子像所有那些超级简单的例子,它是一个足够小,是不真实的,但希望足以让你想象发生了什么事情,而不落入沼泽的一个真实的例子。

在这个例子中,我写的一个组件,它提供了一个由一个特定的导演执导的电影列表。这令人震惊的有用的功能是通过一个单一的方法。

一流的MovieLister ...
    公共电影[] moviesDirectedBy(弦乐ARG){
        清单allMovies = finder.findAll();
        器(Iterator = allMovies.iterator(); it.hasNext();}} {
            电影电影(动画)it.next();
            如果(movie.getDirector()等于(ARG))(); it.remove,
        }
        回报(电影[])allMovies.toArray(新电影[allMovies.size()]);
    }

此功能的实现是幼稚的极端,它要求一个finder对象(我们将在某一时刻)返回它知道每部电影。然后,它只是通过这个名单狩猎返回那些由特定董事。这件特殊的天真,我不会解决,因为它只是这篇文章的真实点的脚手架。

本文的重点是此取景对象,特别是如何连接的利斯特对象与一个特定的Finder对象。之所以出现这种有趣的是,是,我想我的精彩 moviesDirectedBy方法是完全独立的,所有的电影是如何被存储。因此,所有的方法就是指一个发现者,发现者不知道如何应对 的findAll方法。我可以把这个定义一个接口,用于取景器。

公共接口MovieFinder的{
    列出的findAll();
}

现在,这一切都是非常良好的去耦,但在某些时候,我必须拿出一个具体的类,实际上拿出的电影。在这种情况下,我把我的利斯特类的构造函数的代码。

一流的MovieLister ...
  私人MovieFinder的取景器;
  公众的MovieLister(){
    取景器=新ColonDelimitedMovieFinder(“movies1.txt);
  }

实现类的名称来自我得到的事实,我从一个冒号分隔的文本文件列表。饶你的细节,只是有一些实施后,所有的点是。

现在,如果我只是我自己使用这个类,这是所有罚款和花花公子。但是,当我的朋友都渴望这个奇妙的功能不堪重负,想我的程序的副本吗?如果他们还可以存储自己的电影目录,以冒号分隔的文本文件称为“movies1.txt”,那么一切都是美好的。如果他们有不同的名称,他们的电影文件,那么它很容易把在属性文件的文件名 ​​。但是,如果他们有一个完全不同的形式存储他们的电影清单:SQL数据库,XML文件,Web服务,或只是另一种格式的文本文件?在这种情况下,我们需要一个不同的类获取数据。现在,因为我已经定义了一个MovieFinder的接口,这不会改变我的moviesDirectedBy方法。但我仍然需要有一些方式来获得正确的取景器实现的一个实例到位。

[置顶] 控制容器的反转和依赖注入模式_第1张图片

图1:使用一个简单的创作利斯特类的依赖

图1示出了这种情况下的依赖关系。MovieFinder的接口和实施后的MovieLister类是依赖我们宁愿它,如果它是只依赖于接口,但当时我们怎么做一个实例的工作?

P的地产代理监管局在我的书中,我们描述了这种情况下作为一个插件取景器的实现类的程序在编译的时候没有联系到,因为我不知道是什么,我的朋友要使用。相反,我们希望我的利斯特与任何实现,该实现插在以后的某个点,走出了我的手。问题是我怎样才能使该链接,这样我的利斯特类的实现类是无知,但仍然可以说,以一个实例来完成其工作。

扩大成为一个真正的系统,我们可能有几十个这样的服务和组件。在每一种情况下,我们可以抽象我们使用这些组件通过与他们交谈,通过接口(使用适配器组件在设计上没有考虑接口)。但是,如果我们希望以不同的方式部署这个系统,我们需要使用插件来处理与这些服务交互,所以我们可以使用不同的实现在不同的部署。

因此,最核心的问题是我们如何组装这些插件到一个应用程序?这是,这个新品种的轻量级容器的脸,他们普遍都不要使用控制反转的主要问题之一。

控制反转

当这些容器谈论如何,它们非常有用,因为它们实现“控制反转”我结束了,很是不解。控制反转是框架的一个共同的特点,所以说,这些轻量级容器是特殊的,因为他们使用控制反转是像说我的车是特殊的,因为它有轮子。

现在的问题,是什么方面的控制反转?当我第一次跑进控制反转,这是在主控制的用户界面。早期的用户界面,通过应用程序来控制。你将有一个命令序列,如“输入名称”,“输入地址”,你的程序将驱动提示,拿起每一个响应。随着图形(或者甚至是基于屏幕)的UI,UI框架将包含这个主回路和您的程序,而不是在屏幕上的各个领域提供了事件处理程序。该计划的主要控制反转,移动远离你的框架。

对于这个新品种的容器倒置是他们如何查找插件实现。在我幼稚的例子利斯特看着取景器实现直接实例。这将停止取景器是一个插件。这些容器使用的方法,以确保任何用​​户的插件如下一些惯例,允许一个独立的汇编模块注入到利斯特实施。

因此,我认为这种模式,我们需要一个更具体的名称。控制反转是太通用的一个术语,因而人们感到困惑。作为一个结果了很多讨论各种IoC的倡导者,我们定居的名称 依赖注入

我要开始谈论各种形式的依赖注入,但我要指出,这是不是唯一的方法去除从应用程序类的依赖插件实现。你可以用它来做到这一点是其他模式服务定位器,我将讨论后,我做了解释依赖注入。

依赖注入的形式

依赖注入的基本思路是有一个单独的对象,汇编器,填充适当的实现的利斯特类的取景器接口中的一个字段,导致依赖关系图,沿图2线

[置顶] 控制容器的反转和依赖注入模式_第2张图片

图2:依赖注入的依赖

有三种主要类型的依赖注入。我使用他们的名字是构造器注入,setter注入和接口注入。如果你读了这个东西,反转控制在当前的讨论中,你会听到这些称为1型IOC(接口注入),2型IOC(setter注入)和3型IOC(构造函数注入)。我觉得数字名称,而很难记住,这就是为什么我的名字,我这里有。

PicoContainer的构造器注入

首先,我将显示如何使用一个轻量级的容器名为PicoContainer的注入完成我从这里开始的,主要是因为几个同事在ThoughtWorks PicoContainer的(是的,它是一个企业的裙带关系。的发展非常活跃)

PicoContainer的使用构造函数来决定如何注入到利斯特类发现者实现。对于这项工作,在电影的利斯特类需要声明一个构造函数,包括一切需要注入。

一流的MovieLister ...
    公众的MovieLister(MovieFinder的发现者){
        this.finder =取景器;       
    }

取景器本身也将由微微容器中,进行管理,因此将具有文件名的文本文件,它由容器注入。

类ColonMovieFinder ...
    公共ColonMovieFinder(弦乐名){
        this.filename =文件名;
    }

然后微微容器需要告诉实现类与每个接口关联,取景器注入字符串。

    :私人MutablePicoContainer configureContainer(){
        MutablePicoContainer微微=新DefaultPicoContainer();
        参数[] finderParams = {的新ConstantParameter(“movies1.txt”)};
        pico.registerComponentImplementation(MovieFinder.class,ColonMovieFinder.class,finderParams);
        pico.registerComponentImplementation(MovieLister.class);
        返回微微;
    }

此配置代码通常被设置在不同的类。在我们的例子中,每个朋友谁使用我的利斯特可能会写一些自己的设置类适当的配置代码。当然,这是常见,持有这种单独的配置文件中的配置信息。你可以写一个类,读取配置文件并设置适当的容器。PicoContainer的虽然不包含此功能本身,有一个密切相关的项目,被称为纳米容器,提供适当的包装让你有XML配置文件。这种纳米容器将解析XML和,然后配置一个基本微微容器。该项目的理念是分开的配置文件格式,从底层机制。

要使用的容器,你写的代码像这样。

    公共无效testWithPico(){
        MutablePicoContainer微微= configureContainer();
        利斯特的MovieLister =的MovieLister pico.getComponentInstance(MovieLister.class),
        [电影]电影= lister.movi​​esDirectedBy(塞尔吉奥莱昂“);
        的assertEquals(“黄飞鸿在西方”,电影[0]。的getTitle());
    }

虽然在这个例子中,我已经使用构造函数注入,PicoContainer的也支持setter注入,虽然它的开发者喜欢构造函数注入。

Setter注入与Spring

Spring框架是一个广泛的框架的企业Java开发。它包括抽象层的事务,持久化框架,web应用开发和JDBC。PicoContainer的一样,它同时支持构造函数和setter注入,但它的开发者倾向于选择setter注入-这使得它在这个例子中的一个合适的选择。

为了让我的电影利斯特接受注射,我定义了一个该服务的设置方法

一流的MovieLister ...
    私人MovieFinder的取景器;
  公共的无效setFinder(MovieFinder的发现者){
    this.finder =取景器;
  }

同样地,我定义一个setter文件名。

类ColonMovieFinder ...
    公共的无效的setFileName(字符串文件名){
        this.filename =文件名;
    }

第三步是建立配置文件。Spring支持通过XML文件,也可以通过代码的配置,但XML是预期的方式做到这一点的。

    <beans>
        <bean id="MovieLister" class="spring.MovieLister">
            <property name="finder">的
                [local="MovieFinder"/>
            </财产>
        </豆>
        <bean id="MovieFinder" class="spring.ColonMovieFinder">
            <property name="filename">的
                <参数> movies1.txt </值>
            </财产>
        </豆>
    </豆>

然后测试看起来是这样的。

    公共无效testWithSpring()抛出异常
        ApplicationContext的CTX =新FileSystemXmlApplicationContext来(“spring.xml”)的;
        利斯特的MovieLister =的MovieLister ctx.getBean(“的MovieLister”);,
        [电影]电影= lister.movi​​esDirectedBy(塞尔吉奥莱昂“);
        的assertEquals(“黄飞鸿在西方”,电影[0]。的getTitle());
    }

接口注入

第三次注射技术是定义和使用注射接口。Avalon的是,例如,一个框架,使用这种技术的地方。我将讨论多一点后,但在这种情况下,我要使用一些简单的示例代码。

有了这项技术,首先,我定义一个接口,我将使用执行注射。这里的接口注入的电影取景成一个对象。

公共接口InjectFinder的{
    无效injectFinder(MovieFinder的发现者);
}

此接口将被定义的人提供了MovieFinder接口。它需要实现任何类,想使用取景器,如利斯特。

的MovieLister类的实现InjectFinder ...
    公共的无效injectFinder(MovieFinder的发现者){
        this.finder =取景器;
    }

我用类似的方法注入到取景器实现文件名。

公共接口InjectFinderFilename {
    无效injectFilename(字符串文件名);
}
类ColonMovieFinder实现MovieFinder的,InjectFinderFilename ......
    公共的无效injectFilename(字符串文件名){
        this.filename =文件名;
    }

然后,像往常一样,我需要一些配置代码实现连接。为了简单起见,我会做代码。

类测试仪...
    私人集装箱货柜;

     私人的无效configureContainer(){
       集装箱=新集装箱();
       registerComponents();
       registerInjectors();
       container.start();
    }

此配置中有两个阶段,通过查找键注册组件的其他实施例是相当类似的。

类测试仪...
  私人的无效registerComponents(){
    container.registerComponent(“的MovieLister”,MovieLister.class);
    container.registerComponent(“MovieFinder的”ColonMovieFinder.class);
  }

一个新的一步是注册的注射器,将注入的依赖组件。每个注射接口需要一些代码来注入依赖对象。在这里,我这样做与容器注册注射器对象。每个喷油器对象实现喷油器接口。

类测试仪...
  私人的无效registerInjectors(){
    container.registerInjector(InjectFinder.class,container.lookup(MovieFinder的“));
    container.registerInjector(InjectFinderFilename.class,:新FinderFilenameInjector());
  }
公共接口喷油器{
  公共无效注入(目标对象);

}

当写这个容器是一类依赖,它是有道理的组件实现喷油器接口本身,我在这里做的电影取景。对于通用类,如字符串,我用一个内部类的配置代码内。

类ColonMovieFinder实现喷油器......
  公共无效注入(目标对象){
    ((InjectFinder)目标)。injectFinder(本);        
  }
类测试仪...
  公共静态类FinderFilenameInjector的实现喷油器{
    公共无效注入(目标对象){
      ((InjectFinderFilename)的目标)。injectFilename(“movies1.txt”);      
    }
    }

测试,然后使用容器。

类IfaceTester ...
    公共无效testIface(){
      configureContainer();
      的MovieLister利斯特= MovieLister使用container.lookup(由“的MovieLister”);
      [电影]电影= lister.movi​​esDirectedBy(塞尔吉奥莱昂“);
      的assertEquals(“黄飞鸿在西方”,电影[0]。的getTitle());
    }

该容器使用申报的注射接口的依赖关系和喷油器注入正确的家属,要弄清楚。(具体我在这里做的容器实现的技术并不重要,我不会表现出来,因为你只笑)。

使用服务定位器

依赖注入的主要优点是,它消除了依赖关系,它的MovieLister类对具体 MovieFinder的实现。这让我给大腕朋友,并为他们自己的环境插入一个合适的实施。注射液是不是只有这样,才能打破这种依赖,另一个是使用一个服务定位器

服务定位器背后的基本想法是有一个对象,知道如何保持所有的应用程序可能需要的服务。因此,一个服务定位器这个应用程序将有一个方法,返回一个电影取景,当一个人需要。当然这只是一点点的负担转移,我们仍然有定位到利斯特,导致依赖图3

[置顶] 控制容器的反转和依赖注入模式_第3张图片

图3:为服务定位的依赖

在这种情况下,我将使用的ServiceLocator作为单身注册表利斯特可以再使用取景器时,它的实例。

一流的MovieLister ...
    MovieFinder的取景器= ServiceLocator.movi​​eFinder();
类ServiceLocator的...
    公共静态MovieFinder的MovieFinder的(){
        返回soleInstance.movi​​eFinder;
    }
    私人静态的ServiceLocator soleInstance;
    私人MovieFinder的MovieFinder的;

与注入的方式,我们要配置的服务定位器。在这里,我做代码,但它不是很难用一种机制,从配置文件中读取相应的数据。

类测试仪...
    私人无效配置(){
        ServiceLocator.load(新的ServiceLocator(:新ColonMovieFinder(“movies1.txt”)));
    }
类ServiceLocator的...
    公共静态无效负载(ServiceLocator的ARG){
        soleInstance = ARG;
    }

    公共服务定位​​(MovieFinder的MovieFinder的){
        this.movi​​eFinder = MovieFinder的;
    }

下面是测试代码。

类测试仪...
    公共无效testSimple(){
        配置();
        利斯特的MovieLister =新的MovieLister();
        [电影]电影= lister.movi​​esDirectedBy(塞尔吉奥莱昂“);
        的assertEquals(“黄飞鸿在西方”,电影[0]。的getTitle());
    }

我常听到的投诉,这些类型的服务定位器是一件坏事,因为它们是不可测试,因为你不能为他们实现替代。当然,你可以设计他们的生活进入这样的麻烦,但你不必。在这种情况下,服务定位器实例仅仅是一个简单的数据持有人。测试实现我的服务,我可以很容易地创建定位器。

对于更复杂的定位,我可以继承服务定位到注册表的类变量,并通过该子类。我可以改变的静态方法实例上调用一个方法,而不是直接访问实例变量。我可以提供特定于线程的定位器,通过使用特定于线程的存储。所有这一切都可以做到在不改变客户的服务定位器。

认为这是一种方式,服务定位器是一个注册表不是单身。单身提供了一种简单的方式实现一个注册表,但是,执行的决定是很容易改变。

使用定位器隔离接口

的问题与上述简单的方法之一,是供MovieLister依赖于完整的服务定位器类,尽管它仅使用一个服务。我们可以减少使用一个 隔离接口通过这种方式,而不是使用完整的服务定位器接口,利斯特可以宣告位的接口,它需要。

在这种情况下,利斯特供应商也将提供一个定位器接口,它需要得到保持的取景器。

公共接口MovieFinderLocator {
    公共MovieFinder的MovieFinder的();

定位器则需要实现这个接口提供访问一个发现者。

    MovieFinderLocator定位器= ServiceLocator.locator();
    MovieFinder的取景器= locator.movi​​eFinder();
   公共静态ServiceLocator的定位(){
        返回soleInstance;
    }
    公共MovieFinder的MovieFinder的(){
        返回MovieFinder的;
    }
    私人静态的ServiceLocator soleInstance;
    私人MovieFinder的MovieFinder的;

你会注意到,因为我们要使用一个接口,我们不能只是获得服务,通过任何的静态方法。我们必须使用类来获得一个定位器实例,然后用它来得到我们所需要。

动态的服务定位器

上面的例子是静态的,在服务定位器类有方法,你需要为每个服务。这是不是唯一的方式做它,你也可以做一个动态服务定位器,让您任何服务,您需要把它藏匿在运行时,使您的选择。

在这种情况下,服务定位器使用的地图,而不是字段的每个服务,并提供通用的方法,以获得和加载服务。

类ServiceLocator的...
    私人静态的ServiceLocator soleInstance;
    公共静态无效负载(ServiceLocator的ARG){
        soleInstance = ARG;
    }
    私人地图服务=新的HashMap();
    公共静态的对象GetService(弦乐键){
        返回soleInstance.services.get(键);
    }
    公共的无效loadService(串键,对象服务){
        services.put(键,服务);
    }

配置包括适当的键加载服务。

类测试仪...
    私人无效配置(){
        ServiceLocator的定位器=新的ServiceLocator的();
        locator.loadService(MovieFinder的“,:新ColonMovieFinder(”movies1.txt“));
        ServiceLocator.load(定位);
    }

我使用该服务通过使用相同的密钥字符串。

一流的MovieLister ...
    MovieFinder的取景器=(MovieFinder的)ServiceLocator.getService(“MovieFinder的”);

整体来说,我不喜欢这种做法。虽然它肯定是灵活的,这是不是很明确。我可以找出如何达到服务的唯一途径是通过文本键。我更喜欢明确的方法,因为它更容易找到他们正在寻找的接口定义。

同时使用定位器和注射阿瓦隆

依赖注入和服务定位器不一定是相互排斥的概念。两者一起使用的一个很好的例子是Avalon框架。阿瓦隆使用一个服务定位器,但,告诉组件在哪里可以找到定位器使用注射。

Berin Loritsch送我运行的例子使用阿瓦隆我这个简单的版本。

公共类MyMovieLister的实现的MovieLister,可维修{
    私人MovieFinder的取景器;

    公共的无效服务(ServiceManager的经理)抛出ServiceException的{
        取景器= MovieFinder的manager.lookup(由“发现者”);
    } 
      

的服务的方法是一个示例,接口注入,使容器注入到MyMovieLister的服务管理器。服务管理器是一个服务定位器的一个例子。在这个例子中,利斯特不存储经理人在一个领域,而是立刻用它来查找取景器,它确实会存储。

决定使用哪个选项

到目前为止,我已经集中解释我如何看这些模式及其变化。现在我可以开始谈论其利弊,以帮助找出哪些以及何时使用。

服务定位与依赖注入

之间最根本的选择是服务定位器和依赖注入。第一点是,这两种实现提供了基本脱钩,缺少天真的例子 - 在这两种情况下,应用程序代码是独立的服务接口的具体实施。两种模式之间的重要区别是关于怎样实施所提供的应用程序类。服务定位器应用程序类的消息定位明确要求。注射有没有明确的要求,在服务中出现的应用程序类 - 因此控制反转。

控制反转是框架的一个共同的特点,但它的东西是要付出代价的。它往往是很难理解的,并会导致问题,当您试图调试。所以整体来说,我宁可不去,除非我需要它。这是不是说这是一件坏事,只是我认为它需要在更简单的替代自圆其说。

关键的区别是,与服务定位,服务的每一个用户有一个依赖的定位。定位器可以隐藏其他实现的依赖,但你需要看到的位置。因此,定位和喷油器之间的决策依赖于这种依赖是否是一个问题。

使用依赖注入可以帮助使其更容易看到组件的依赖关系是什么。有了依赖注入器,你就可以看,如构造函数注入机制,看到的依赖。随着服务定位器,你要搜索的源代码调用定位。现代的IDE与找到引用功能使它更容易些,但它仍然是不容易,因为在构造函数或设置方法。

这取决于很多的用户的服务的性质。如果你正在构建一个应用程序使用服务的各种类别,然后从应用程序类的依赖关系的定位是没什么大不了的。在我的例子中,给人一种电影李斯特我的朋友,然后使用一个服务定位器工作得很好。所有他们需要做的是配置定位在正确的服务实现挂钩,无论是通过一些配置代码,或通过一个配置文件。在这种情况下,我看不出有注射器反演提供任何令人信服的。

不同之处在于,如果利斯特是一个组件,我提供给其他人正在编写一个应用程序,。在这种情况下,我不知道多少,我的客户要使用的服务定位器的API。每个客户可能有自己的不兼容的服务定位器。我能得到这个周围的一些使用分离接口。每个客户可以写自己的定位我的接口相匹配的适配器,但在任何情况下,我仍然需要看到的第一定位器来查找我的特定的接口。一旦适配器出现那么简单,直接连接到一个定位器开始打滑。

由于与喷射器没有从一个组件到喷射器的依赖性,该组件无法获得进一步的服务,一旦它被配置从喷射器。

人们宁愿依赖注入给一个常见的​​原因是,它使测试更容易。这里的要点是,做测试,你需要很容易地取代存根或模拟实际服务实现。但是,真的是没有依赖注入和服务定位之间的区别就在这里:两者都非常适合存根。我怀疑这种观察来自人不作出努力,以确保他们的服务定位器,可以很容易地取代的项目。这是持续测试的帮助,如果你不能轻易存根服务测试,那么这意味着一个严重的问题与您的设计。

当然,测试的问题加剧了组件的环境中是非常具侵扰性,如Java的EJB框架。我的看法是,这类框架应该尽量减少其影响的应用程序代码时,特别是不应该做的东西,放慢编辑执行周期。使用插件来代替重量级组件做了很多帮助这一进程,这是非常重要的做法,如测试驱动开发。

因此,首要的问题是谁的人编写代码,预计要使用的应用程序之外的作家的控制。在这种情况下,即使是最小的假设有关服务定位是一个问题。

构造与Setter注入

对于服务组合,你总是有一些公约,以电汇的东西放在一起。注射的优点主要是,它需要非常简单的约定 - 至少在构造函数和setter注入。你没有做你的组件有什么奇怪的,这是相当简单的注射器得到的一切配置。

接口注入更具侵入性的,因为你写了很多的接口得到的东西都整理出来。对于容器所需的接口,如在Avalon的方法的一小部分,这是不是太糟糕了。但它是一个大量的工作用于装配组件和依赖,这就是为什么时下的轻量级容器setter和构造器注入。

setter和构造器注入之间的选择是有趣的,因为它反映了面向对象编程的更普遍的问题 - 你应该填写字段在构造函数或setter方法​​。

我的长期运行默认的对象是尽可能在施工时,创建有效的对象。这个建议可以追溯到Kent Beck的Smalltalk的最佳实践模式:构造方法和构造器参数的方法。带参数的构造函数,给你一个明确的说法,这是什么意思在明显的地方创建一个有效的对象。如果有一个以上的方式做到这一点,创建多个构造函数,显示不同的组合。

构造函数初始化的另一个优点是,它可以让你清楚地隐藏任何领域都是不可变的,根本就没有提供一个setter。我觉得这是很重要的-如果事情不应该改变,那么一个setter缺乏沟通,这非常好。如果您使用的setter方法用于初始化,那么这可以成为一种痛苦。(事实上,在这些情况下,我宁愿避免了通常设置惯例,我宁愿这样的方法initFoo,强调它的东西,你应该只在出生时做的。)

但是,任何情况也有例外。构造函数的参数,如果你有很多东西可以看一下凌乱,尤其是在没有关键字参数的语言。这是真的,一个长期的构造往往是过忙的对象,应拆分的标志,但也有情况时,这就是你所需要的。

如果你有多种方式来构建一个有效的对象,它可以很难显示通过构造,因为构造只能改变参数的数量和类型。这是,当工厂方法来发挥作用,这些都可以组合使用私有构造函数和setter来实现他们的工作。经典的组件装配工厂方法的问题是,他们通常被看作是静态方法,你不能拥有这些接口上。你可以让一个工厂类,但当时只是变成了另一个服务实例。工厂服务通常是一个很好的策略,但你仍然有实例的工厂使用这里的技术之一。

构造也受苦,如果你有简单的参数,如字符串。随着setter注入,你可以给每个setter方法​​的名称,以表明什么是应该做的字符串。构造函数,你只是靠的位置上,这是更难。

如果您有多个构造函数和继承,那么事情可以得到特别别扭。为了初始化一切你必须提供构造函数转发给每一个超类的构造,同时还增加了自己的论点。这可能会导致一个更大的爆炸的构造函数。

尽管有这些缺陷,我的选择是开始构造函数注入,但要准备切换到setter注入,尽快上文所述启动的问题我已经成为一个问题。

这个问题已经导致了很多辩论,各队之间的依赖喷射器作为其框架的一部分。然而,它似乎构建这些框架的大多数人已经意识到,重要的是要支持这两种机制,即使有偏爱其中之一。

代码或配置文件

一个单独的,但经常被混为一谈的问题是,是否使用一个API来连接服务配置文件或代码。对于大多数应用程序,有可能被部署在很多地方,一个单独的配置文件通常最有意义。几乎所有的时间,这将是一个XML文件,这是有道理的。然而,有情况下,它更容易使用的程序代码进行组装。一种情况是,你有一个简单的应用程序,没有很多的部署变化。在这种情况下,一个位的代码可以更清楚不是一个单独的XML文件。

一个对比的情况是其中的组件是相当复杂的,涉及有条件的步骤。一旦你开始越来越接近编程语言,那么XML开始打破,这是更好地使用真实的语言,所有的语法来写一个明确的计划。然后你写一个建设者的类,它的装配。如果你有独特的建设者场景,你可提供数生成器类,并使用一个简单的配置文件,它们之间进行选择。

我常常在想,人是过于急切的来定义配置文件。通常一种编程语言,使得一个简单而强大的配置机制。现代语言可以很容易地编制小,可用于较大的系统插件组装装配。如果编译是一种痛苦,再有脚本语言也可以很好地工作。

人们常常说,配置文件不应该使用的编程语言,因为他们需要非程序员编辑。但往往是这样的情况吗?难道人们真的指望非程序员改变一个复杂的服务器端应用程序的事务隔离级别?非语言配置文件的工作以及他们是简单的程度。如果他们变得复杂,那么它时间想想使用适当的编程语言。

有一件事,我们在Java世界中看到的那一刻是一个刺耳的配置文件,其中每一个部件都有自己的配置文件,这是其他人的不同。如果您使用的这些组件打,你可以很容易地与十几配置文件保持同步。

我的建议是要始终编程接口轻松地做所有的配置,然后把一个单独的配置文件作为一个可选功能提供了一种方法。使用的编程接口,您可以轻松地建立配置文件处理。如果你正在编写一个组件,然后离开它到你的用户是否使用的编程接口,您的配置文件格式,或写自己的自定义配置文件格式,并能配合它的编程接口

使用分离配置

在所有这一切的重要问题是,以确保其使用服务的配置是分开的。事实上,这是一个基本的设计原则,坐在接口与实现分离的。这是在一个面向对象的程序,当条件逻辑决定哪一个类实例时,我们看到的东西,然后,有条件的多态性,而不是通过重复的条件代码都是通过未来的评估。

如果这种分离在一个单一的代码库是有用的,这是特别重要的,当你使用的组件和服务,如外来元素。第一个问题是,是否要推迟实现类的选择特定的部署。如果是这样,你需要使用一些执行的插件。一旦你使用插件,那么它必须组装的插件应用程序的其余部分分开,这样你可以很容易地替换不同的配置不同的部署。你如何做到这一点是次要的。此配置机制,可以配置一个服务定位器,或使用注射液直接配置对象。

一些进一步的问题

在这篇文章中,我已经集中使用依赖注入和服务定位器服务配置的基本问题。有一些更多的话题,玩到这一点,也值得关注,但我还没来得及挖成。尤其是生命周期行为的问题。某些组件有不同的生命周期事件:停止和启动的实例。另一个问题是这些容器使用面向方面的想法与兴趣与日俱增。虽然我还没有考虑这种材料在文章中的那一刻,我希望写更多关于这个延伸这篇文章或者写另一。

看着致力于轻量级容器的网站,你可以找到很多关于这些想法。冲浪 PicoContainer的春天网站会导致更多的讨论这些问题,并一开始就一些进一步的问题。

总结思考

目前急于轻量级容器都有一个共同的,他们是如何做到服务组装的基本模式 - 依赖注入模式。依赖注入是一个有用的替代服务定位器。当构建应用程序类的两个大体相当,但我认为服务定位器有轻微的边缘,由于其更直接的行为。然而,如果你正在建设类可用于多个应用程序,然后依赖注入是一个更好的选择。

如果你使用依赖注入有许多款式可供选择之间。我会建议你遵循构造函数注入,除非你碰到的具体问题与方法,在这种情况下,切换到setter注入。如果您选择建立或获得一个容器,支持构造函数和setter注入。

服务定位之间的选择和依赖注入是比服务配置服务,在应用程序中使用分离的原则,那么重要。

原文链接:http://martinfowler.com/articles/injection.html


你可能感兴趣的:(依赖注入,控制反转,spring.net)