控制容器的反转和依赖注入模式

在Java社区中,有一个轻量级容器的涌入,帮助将组件从不同的项目组装成一个有凝聚力的应用程序。这些容器的底层是它们如何执行布线的一个通用模式,他们在“控制反转”这个非常通用的名字下提到的一个概念。在本文中,我将深入探讨这种模式的工作原理,在更具体的名称“依赖注入”下,并将其与服务定位器替代方案进行对比。它们之间的选择比从使用中分离配置的原则更不重要。

关于企业Java世界的一个有趣的事情是在构建主流J2EE技术的替代品方面的大量活动,其中大部分都发生在开源环境中。很多这是对主流J2EE世界中的重量级复杂性的反应,但其中很多人也在探索替代方案,并用创造性的想法进行交流。要处理的一个常见问题是如何将不同的元素连接在一起:当不同的团队在彼此不太了解的情况下构建它们时,如何将这个WebController体系结构与支持的数据库接口结合在一起?许多框架都试图解决这个问题,以及若干分支,以提供从不同层组装组件的一般能力。这些通常被称为轻型容器,示例包括微型集装箱,和春天.

这些容器的基础是一些有趣的设计原则,bet188足球超越这些特定容器和Java平台的事物。在这里,我想开始探索其中的一些原则。我使用的例子是Java,但和我写的大多数作品一样,这些原则同样适用于其他OO环境,尤其是.NET。


组件和服务

将元素连接在一起的主题几乎将我拖到了围绕术语服务和组件的复杂术语问题中。你可以轻松地在这些事物的定义上找到长而矛盾的文章。为了我的目的,这里是这些重载术语的当前用法。

我用component来表示一个要使用的软件。没有变化,一个不受组件编写器控制的应用程序。“不更改”是指使用应用程序不会更改组件的源代码,尽管它们可以通过在组件编写器允许的范围内扩展来更改组件的行为。

服务类似于一个组件,因为它被外国应用程序使用。主要的区别在于我希望在本地使用组件(想想jar文件,装配,动态链接库或源导入)。服务将通过一些远程接口远程使用,同步或异步(如Web服务、消息传递系统、RPC,或套接字。

我在本文中主要使用服务,但是,许多相同的语言也可以应用于本地组件。实际上,您通常需要某种本地组件框架来轻松访问远程服务。但是写“组件或服务”读写起来很累,现在的服务更时尚。


幼稚的例子

为了使所有这些更具体,我将使用一个运行示例来讨论所有这些。像我所有的例子一样,这是非常简单的例子之一;小到不真实,但希望你能在不陷入一个真实例子的情况下想象正在发生的事情。

在这个例子中,我编写了一个组件,它提供了一个特定导演导演的电影列表。这个非常有用的函数是通过一个方法实现的。

类movielister…

public movie[]moviesdirectedby(string arg)list allmovies=finder.findall();for(iterator it=allmovies.iterator();it.hasNext();)电影电影=(电影)it.next();如果(!)movie.getdirector().equals(arg))it.remove();}返回(movie[])allmovies.toarray(new movie[allmovies.size()]);}

这个功能的实现极端幼稚,ITask是一个finder对象(稍后我们将到达),用于返回它所知道的每一个电影。然后,它只需搜索这个列表,返回特定导演指导的影片。这件我不想解决的幼稚的事,因为它只是本文真正意义上的脚手架。

本文的重点是这个finder对象,或者特别是如何将Lister对象与特定的finderObject连接起来。之所以有趣是因为我想要我的精彩由导演的电影方法完全独立于所有电影的存储方式。所以所有的方法都是引用一个查找器,而finder所做的就是知道如何响应芬德尔方法。我可以通过为查找者定义一个界面来实现这一点。

公共接口moviefinder list findall();

现在所有这些都很好地分离了,但在某个时刻,我必须想出一个具体的类来真正想出这些电影。在本例中,我将此代码放入MyLister类的构造函数中。

类movielister…

私人电影取景器;public movielister()finder=new colondelimitedmoviefinder(“movies1.txt”);}

实现类的名称来自这样一个事实:我从冒号分隔的文本文件中获取列表。我会留下一些细节,毕竟,关键是有一些实现。

如果我只为自己使用这个课程,这都很漂亮。但是,当我的朋友们被Adesire的出色功能所压倒,想要一份MyProgram时,会发生什么呢?如果他们还将电影列表存储在一个冒号分隔的文本文件“movies1.txt”中,那么一切都很好。如果他们的电影文件有不同的名字,然后很容易将文件名放在属性文件中。但是如果他们有完全不同的存储电影列表的形式:一个sqldatabase,一个XML文件,一个Web服务,或者只是另一种文本文件格式?在这种情况下,我们需要一个不同的类来获取数据。现在因为我已经定义了取景器接口,这不会影响我的由导演的电影方法。但我仍然需要一些方法来实现正确的finder实现。

图1:使用Lister类中的简单创建的依赖项

图1显示此情况的依赖项。这个莫比利斯特类依赖于取景器接口和实现。我们希望它只依赖于接口,但是我们如何创建一个实例来使用呢?

在我的书里EAA P,我们把这种情况描述为插件.finder的implementation类没有链接到程序atcompile time,因为我不知道我的朋友将要使用什么,所以我们希望我的列表器与任何实现一起工作,为了在以后插入实现,出于我的手。问题是,我如何使该链接使我的Lister类不知道实现类,但仍然可以和一个立场说话来做它的工作。

把它扩展到一个真正的系统中,我们可能有几十个suchs服务和组件。在每种情况下,我们都可以通过一个接口(如果组件的设计没有考虑到接口,则使用适配器)来抽象这些组件的使用,但是如果我们希望以不同的方式部署此系统,bet188足球我们需要使用插件来处理与这些服务的交互,以便在不同的部署中使用不同的实现。

所以核心问题是我们如何将这些插件组装成应用程序?这是新一代轻型集装箱面临的主要问题之一,一般来说,他们都是用控制的版本来实现的。


控制反转

当这些容器谈论它们如何如此有用,因为它们实现了“控制反转”,我最终感到非常困惑。控制反转是框架的共同特征,所以说这些轻型容器是特殊的,因为它们使用反向控制,就像说我的车是特殊的,因为它有轮子。

问题是:“他们在控制的哪一方面发生了逆转?”当我第一次遇到控制反转时,它在一个用户界面的主控制下。早期的用户界面由应用程序控制。您将拥有一系列命令,如“entername”,“输入地址”;您的程序将驱动提示并对每个提示作出响应。使用图形(甚至是基于屏幕的)ui框架,ui框架将包含这个主循环和您的程序,而不是为屏幕上的各个字段提供事件处理程序。程序的主控颠倒了,离开你到了办公室。

对于这种新的容器类型,倒置是关于它们如何查找插件实现。在我幼稚的示例中,listerlooked通过直接实例化finder实现。这会阻止finder成为插件。这些容器使用的方法是确保插件的任何用户遵循某种约定,允许单独的汇编程序模块将实现注入到列表器中。

因此,我认为我们需要一个更具体的模式名称。控制反转是一个非常通用的术语,因此人们觉得这很困惑。因此,我们与不同的倡导者进行了大量的讨论,确定了这个名字。依赖注入.

我将从各种形式的注射开始讲,但现在我要指出的是,这并不是从应用程序类中删除对插件实现的依赖关系的唯一方法。您可以使用的另一种模式是ServiceLocator,在我解释完依赖性拒绝之后,我会讨论这个问题。


依赖注入的形式

依赖注入的基本思想是有一个独立的对象,汇编程序,它用finder接口的适当实现填充Lister类中的字段,从而形成沿图2

图2:DependencyInjector的依赖项

依赖注入主要有三种类型。我想他们的名字是构造函数注入,塞特注射,和面内注射。如果你在当前关于控制反转的讨论中读到这些东西,你会听到这些被提到的1型IOC(接口注入)。类型2 IOC(setter injection)和类型3 IOC(constructor injection)。我觉得数字名字很难记住,这就是为什么我用我这里的名字。

用Picocontainer进行构造器注入

我将从演示如何使用名为微型集装箱.我从这里开始主要是因为我的一些同事在ThoughtWorks非常积极地发展PicoContainer(是的,这是一种社团主义。)

PicoContainer使用一个构造函数来决定如何将Afinder实现注入到Lister类中。为了这个工作,movie lister类需要声明一个构造函数,该构造函数包含需要注入的所有内容。

类movielister…

public movielister(moviefinder finder)this.finder=finder;}

finder本身也将由pico容器管理,因此容器会将文本文件的文件名注入其中。

类ColonMovieFinder…

public colonmoviefinder(字符串文件名)this.filename=filename;}

然后需要告诉pico容器要与每个接口关联的实现类,以及要注入到查找器中的字符串。

private mutablePicoContainer configureContainer()mutablePicoContainer pico=new defaultPicoContainer();参数[]finderParams=new ConstantParameter(“movies1.txt”)pico.registercomponentimplementation(moviefinder.class,colonmoviefinder.class,Fiffer-PARAMS);pico.寄存器组件实现(movielister.class);返回皮卡;}

此配置代码通常在不同的类中设置。例如,每个使用我的列表器的朋友都可以在自己的安装类中编写适当的配置代码。当然,将这种配置信息单独保存在配置文件中是很常见的。您可以编写一个类来读取配置文件并适当地设置容器。虽然PicoContainer本身不包含此功能,有一个密切相关的项目叫做NanoContainer,它提供了适当的包装器,允许您使用XML配置文件。这样的nano容器将解析XML,然后配置底层的pico容器。项目的哲学是将配置文件格式从底层机制中分离出来。

要使用容器,您需要编写类似这样的代码。

public void testWithPico()mutablePicoContainer pico=configureContainer();movielister lister=(movielister)pico.getcomponentInstance(movielister.class);movie[]movies=lister.moviesddirectedby(“sergio leone”);断言等于(“在西方曾经”,电影[0].getTitle());

虽然在本例中我使用了构造函数注入,但Picocontainer还支持setter注入,尽管它的开发人员更喜欢构造函数注入。

带弹簧的固定器注射

这个弹簧骨架是一个广泛的企业Java开发框架。它包括交易的抽象层,持久性框架,WebApplication开发和JDBC。像Picocontainer一样,它支持BothConstructor和Setter注入,但是它的开发人员倾向于使用setter注入——这使得它成为这个示例的合适选择。

为了让我的电影列表器接受注入,我为该服务定义了一个设置方法

类movielister…

private moviefinder finder;public void setfinder(moviefinder finder)this.finder=finder;

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

类ColonMovieFinder…

public void setfilename(字符串文件名)this.filename=filename;}

第三步是为文件设置配置。Spring支持通过XML文件和代码进行配置,但XML是实现配置的预期方式。

       
    
        
        
         
            
          
        
         
    
        
    
        
        
         
            
          
           MOVES1.1.TXT
          
        
         
    
        

       

然后测试看起来像这样。

public void testwithSpring()引发异常applicationContext ctx=new filesystemxmlapplicationContext(“spring.xml”);movielister lister=(movielister)ctx.getbean(“movielister”);movie[]movies=lister.moviesddirectedby(“sergio leone”);断言等于(“在西方曾经”,电影[0].getTitle());

界面注入

第三种注入技术是定义和使用注入接口。阿瓦隆这是一个在某些地方使用此技术的框架示例。稍后我再谈这个,但在本例中,我将把它与一些简单的示例代码一起使用。

使用这种技术,我首先定义一个接口,我将使用它来执行注入。这是将电影查找器插入对象的接口。

公共接口injectfinder void injectfinder(moviefinder finder);

这个接口将由提供moviefinder接口的人定义。它需要由任何想要使用finder的类实现,例如列表器。

类movielister实现InjectFinder

public void injectfinder(moviefinder finder)this.finder=finder;}

我使用类似的方法将文件名注入到finder实现中。

公共接口injectfinderFileName void injectFileName(字符串文件名);

类colonmoviefinder实现moviefinder,InjectFinderFileName…

public void injectFileName(字符串文件名)this.filename=filename;}

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

类测试仪…

私人集装箱;private void configureContainer()container=new container();注册表组件();寄存器();container.start();}

这种配置有两个阶段,注册组件这个粗略的查找键与其他示例非常相似。

类测试仪…

private void registercomponents()container.registercomponent(“movielister”,movielister.类);container.registercomponent(“moviefinder”,colonmoviefinder.class);}

一个新的步骤是注册将注入驱动部件的喷油器。每个注入接口都需要一些代码来注入依赖对象。在这里,我用容器来注册对象。每个注入器对象实现注入器接口。

类测试仪…

private void registerInjectors()container.registerInjector(injectFinder.class,container.lookup(“moviefinder”);container.registerInjector(injectFinderFileName.class,新finderFilenameInjector());}
公共接口注入器公共空注入(对象目标);

当dependent是为此容器编写的类时,组件实现注入器接口本身是有意义的,就像我在这里用取景器一样。对于泛型类,例如字符串,我在配置代码中使用了一个内部类。

类colonmoviefinder实现注入器…

公共void inject(object target)(injectfinder)target).injectfinder(this);}

类测试仪…

公共静态类finderfilenameinjector实现injector公共void inject(object target)((injectfinderfilename)target).injectfilename(“movies1.txt”);}

然后测试使用容器。

类测试仪…

public void testiface()configureContainer();movielister lister=(movielister)container.lookup(“movielister”);movie[]movies=lister.moviesddirectedby(“sergio leone”);断言等于(“在西方曾经”,电影[0].getTitle());}

容器使用共享的注入接口来计算依赖项,并使用注入器来注入正确的依赖项。(我在这里所做的特定容器实现对技术不重要,我不会让你看,因为你只会笑。)


使用服务定位器

依赖注入器的主要优点是它消除了莫比利斯特在混凝土上有类取景器实施。这让我可以把它赠给朋友,让他们为自己的环境插入合适的实现。注射不是打破这种依赖的唯一方法,另一种方法是使用服务定位.

服务定位器背后的基本思想是让一个对象知道如何获取应用程序可能需要的所有服务。所以这个应用程序的服务定位器会有一个amethod,在需要时返回一个电影查找器。当然,这只是稍微减轻了负担,我们还得把定位器拿到列表器里,导致图3

图3:ServiceLocator的依赖关系

在这种情况下,我将使用ServiceLocator作为单例登记处.然后列表器可以使用它来获取查找器。

类movielister…

moviefinder finder=serviceLocator.moviefinder();

类服务定位器…

public static moviefinder moviefinder()返回soleinstance.moviefinder;}私有静态服务定位器soleinstance;私人电影导演;

就像注射法一样,我们必须配置服务定位器。在这里我用代码来做,但是使用一个从配置文件中读取适当数据的机制并不难。

类测试仪…

private void configure()servicelocator.load(new servicelocator(new colonmoviefinder(“movies1.txt”));}

类服务定位器…

公共静态空负荷(serviceLocator arg)soleInstance=arg;}公共服务定位器(moviefinder moviefinder)this.moviefinder=moviefinder;}

这是测试代码。

类测试仪…

public void testsimple()configure();movielister lister=新movielister();movie[]movies=lister.moviesddirectedby(“sergio leone”);断言等于(“在西方曾经”,电影[0].getTitle());}

我经常听到抱怨,这些类型的服务定位器是一件坏事,因为它们是不可测试的,因为您不能用它们替代实现。当然,你可以设计得很糟糕,让他们陷入bet188足球这种麻烦,但你不必。在这种情况下,服务定位器实例只是一个简单的数据容器,我可以用myservices的测试实现轻松地创建定位器。

对于更复杂的定位器,我可以将服务定位器子类化,并将该子类传递到注册表的类变量中。我可以更改静态方法来调用实例上的方法,而不是直接访问实例变量。我可以通过使用线程特定的存储来提供线程特定的定位器。所有这些都可以在不更改服务定位器的客户端的情况下完成。

考虑到这一点的一种方法是,服务定位器是一个注册器,而不是一个单独的注册器。单例提供了实现注册表的简单方法,但这个实施决策很容易改变。

使用定位器的隔离接口

上述简单方法的问题之一,是说movielister依赖于完整的服务定位器类,尽管它只使用一个服务。我们可以通过使用角色接口.那样,而不是使用完整的ServiceLocator接口,列表器可以只声明它需要的接口位。

在这种情况下,列表器的提供者也会提供一个定位器接口,它需要这个接口来获取索引。

公共接口moviefinderlocator public moviefinder moviefinder();

然后定位器需要实现这个接口来提供对查找器的访问。

moviefinder locator locator=servicelocator.locator();moviefinder=locator.moviefinder();
public static servicelocator locator()返回soleinstance;}public moviewinder moviewinder()返回moviewinder;}私有静态服务定位器soleinstance;私人电影导演;

您会注意到,由于我们想要使用一个接口,我们不能再通过静态方法访问服务了。我们必须使用这个类来获取一个定位器实例,然后使用它来获取我们需要的。

动态服务定位器

上面的例子是静态的,其中,service locatorClass为您需要的每个服务都有方法。这不是唯一的方法,您还可以创建一个动态服务定位器,它允许您将所需的任何服务存储在其中,并在运行时创建您的服务。

在这种情况下,服务定位器为每个服务使用一个映射而不是字段,并提供获取和加载服务的通用方法。

类服务定位器…

私有静态服务定位器soleinstance;公共静态空负荷(serviceLocator arg)soleInstance=arg;}私有映射服务=new hashmap();public static object getservice(string key)返回soleinstance.services.get(key);}public void loadservice(字符串键,对象服务)服务。放置(键,服务);}

配置涉及使用适当的键加载服务。

类测试仪…

private void configure()serviceLocator locator=new serviceLocator();locator.loadservice(“moviefinder”,新的colonmoviefinder(“movies1.txt”);服务定位器。加载(定位器);}

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

类movielister…

moviefinder finder=(moviefinder)serviceLocator.getService(“moviefinder”);

总的来说,我不喜欢这种方法。虽然它确实很灵活,这不是很明确。我唯一能找到如何通过文本键来实现服务的方法。我更喜欢显式方法,因为通过查看InterfaceDefinitions更容易找到它们的位置。

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

依赖项注入和服务定位器不一定是本质上唯一的概念。使用bothtogether的一个好例子是Avalon框架。Avalon使用服务定位器,但使用注入告诉组件在哪里找到定位器。

白令·洛里奇用阿瓦隆给我发了这个简单版本的Myrunning示例。

公共类mymovielister实现movielister,可维修私人电影取景器;public void service(service manager manager)throws serviceexception finder=(moviefinder)manager.lookup(“finder”);}

服务方法是接口注入的一个例子,允许容器将服务管理器注入到omymoveilister中。服务管理器是ServiceLocator的一个示例。在本例中,lister不将manager存储在字段中,相反,它会立即使用它来查找它存储的查找工具。


决定使用哪个选项

到目前为止,我集中在解释我如何看待这些模式及其变化。现在我可以开始讨论它们的优缺点,以帮助找出使用哪一种以及何时使用。

服务定位器与依赖注入

基本的选择是在服务定位器和DependencyInjection之间。第一点是,这两个实现都提供了基本的分离,这在简单的示例中是缺失的——在Bothcases中,应用程序代码独立于服务接口的具体实现。两个参数之间的重要区别在于如何将该实现提供给应用程序类。对于服务定位器,应用程序类通过发送给定位器的消息明确地请求它。注射时,没有明确要求,该服务出现在应用程序类中,即控制反转。

控制反转是框架的一个共同特征。但这是有代价的。当您尝试调试时,它往往难以理解并导致问题。所以总的来说,除非我需要它,否则我宁愿避免它。这并不是说这是件坏事,只是我认为它需要用更直接的选择来证明自己的合理性。

关键的区别在于,对于服务定位器,服务的每个用户都依赖于该定位器。定位器可以隐藏对其他实现的依赖关系,但你确实需要看看这个定位器。因此,定位器和注入器之间的决定取决于这种依赖性是否是一个问题。

使用依赖项注入可以帮助更容易地看到组件依赖项是什么。使用依赖注入器,您可以查看注入机制,如施工人员,看看它们的结局。使用服务定位器,您必须搜索源代码以查找对定位器的调用。具有查找引用功能的现代IDES使这变得更容易,但这仍然不如观察结构或设置方法简单。

这很大程度上取决于服务用户的性质。如果您正在构建使用服务的各种类的应用程序,那么,从应用程序类到定位器的依赖关系并不是什么大问题。在我给朋友看电影的例子中,然后使用服务定位器工作得很好。他们所需要做的就是配置定位器以钩住正确的服务实现,通过一些配置代码或配置文件。在这种情况下,我不认为投影者的倒装提供了任何令人信服的东西。

如果列表器是我提供给其他人正在编写的应用程序的组件,则会有不同。在这种情况下,我对我的客户将要使用的服务定位器的API不太了解。每个客户可能都有自己的不兼容服务定位器。我可以通过使用这些集成的接口来绕过其中的一些问题。每个客户都可以编写一个适配器,使其与定位器的接口相匹配,但无论如何,我仍然需要看到第一个定位器来查找我的特定接口。一旦适配器出现,直接连接到定位器的简单性就开始下滑。

因为有了注入器,你就不需要依赖于注入器的组成部分,配置后,组件无法从注入器获得进一步的服务。

人们喜欢依赖性注射的一个常见原因是它使测试更容易。这里的要点是,要进行测试,您需要用存根或套接字轻松地替换真实的服务实现。然而,DependencyInjection和ServiceLocator之间并没有什么区别:两者都非常适合存根。我想这个观察来自于那些人们不努力确保他们的服务定位器可以容易地被替换的项目。这就是持续测试的帮助所在,如果你不能临时中断测试服务,这意味着你的设计有严重的问题。bet188足球

当然,测试问题会因为侵入性很强的组件环境而加剧,比如Java的EJB框架。我的观点是,这些类型的框架应该尽量减少它们对应用程序代码的影响,尤其不应该做那些减慢编辑执行周期的事情。使用插件替换重量级组件对这个过程有很大帮助,这对于诸如测试驱动开发之类的实践是至关重要的。

因此,主要的问题是编写代码的人,这些代码希望在编写者控制之外的应用程序中使用。在这些情况下,即使是对服务定位器的最小假设也是一个问题。

构造函数与setter注入

对于服务组合,为了把事情联系起来,你总要有一些惯例。注入的优点主要是它需要非常简单的约定——至少对于构造函数和setter注入是如此。你不必在你的组件中做任何奇怪的事情,这对于注入器来说是一个公平的前进方向,它可以使所有的东西都配置好。

接口注入更具侵略性,因为你必须写很多接口来把所有的事情都解决。对于容器所需的一小组接口,比如在阿瓦隆的方法中,这还不错。但是它在组装组件和依赖关系方面做了很多工作,这就是为什么当前轻量容器的作物与setter和constructor注入一起使用的原因。

setter和constructor注入之间的选择是对的,因为它反映了面向对象编程的一个更普遍的问题——应该在constructor或setter中填充字段。

我对对象的长期默认值是尽可能多的,以便在构建时创建有效的对象。这个建议可以追溯到tokent beck的Smalltalk最佳实践模式:构造函数方法和构造函数参数方法。带参数的构造函数为您提供了一条清晰的语句,说明在异常位置创建有效对象意味着什么。如果有不止一种方法可以做到,创建显示不同组合的多个结构。

构造函数初始化的另一个优点是,它允许您通过不提供setter来清楚地隐藏任何不可变的字段。我认为这很重要——如果有什么东西不应该改变,那么缺少一个setter就可以很好地传达这一点。如果使用setters进行初始化,然后这会变成一种痛苦。(在这些情况下,我更喜欢避免通常的设置约定,我更喜欢这样的方法因特福,强调你只在出生时才应该这样做。)

但在任何情况下都有例外。如果你有大量的构造器参数,事情看起来会很混乱,尤其是没有关键字参数的嵌入语言。的确,longconstructor通常是一个过度繁忙的对象的标志,该对象应该被拆分,但有些情况下,这正是你需要的。

如果您有多种方法来构造有效的对象,很难通过构造函数来显示这一点,因为构造函数只能根据参数的数量和类型变化。这就是工厂方法发挥作用的时候,它们可以使用privateconstructors和setter的组合来实现它们的工作。传统的部件装配工厂方法的问题是,它们通常被视为静态方法,你不能在脸上看到这些。你可以参加工厂课程,但那只是因为其他服务实例。工厂服务通常是一个很好的策略,但是您仍然需要使用这里的技术之一来实例化工厂。

如果您有简单的参数(如字符串),构造函数也会受到影响。通过setter注入,您可以给每个setter一个名称来指示字符串应该做什么。有了建设者,你只需要依靠这个职位,这很难理解。

如果您有多个构造函数和继承,然后事情会变得特别尴尬。为了初始化所有内容,必须提供构造函数以转发到每个超级类构造函数,同时添加您自己的参数。这可能导致施工人员的更大爆炸。

尽管有缺点,我还是倾向于从构造函数注入开始,但是准备好切换到setter-injection assoon,因为我上面概述的问题开始成为一个问题。

这个问题引起了不同群体之间的争论,他们提供依赖注入器作为框架的一部分。然而,似乎大多数构建这些框架的人都意识到支持bothmechanism很重要,即使对其中一个有偏好。

代码或配置文件

一个单独但经常被混淆的问题是,是使用配置文件还是API上的代码来连接服务。对于大多数可能部署在许多地方的应用程序,分离配置文件通常最有意义。几乎所有时候这都是一个XML文件,这是有道理的。然而,在某些情况下,使用程序代码进行组装更容易。一种情况是,您有一个简单的应用程序,但没有很多部署变量。在这种情况下,一些代码可能比单独的XML文件更清晰。

相反的情况是程序集非常复杂,涉及条件步骤。一旦你开始接近编程语言,那么XML就开始分解,最好使用一种真正的语言,它有所有的语法来编写一个清晰的程序,然后你编写一个编译类来完成汇编。如果您有不同的构建器场景,您可以提供几个构建器类,并使用一个简单的配置文件在它们之间进行选择。

我经常认为人们对配置文件过于渴望。通常,编程语言会使配置机制具有前瞻性和强大的功能。现代语言扫描很容易编译小型汇编程序,可用于汇编大型系统的插件。如果汇编是一种痛苦,还有一些脚本语言也可以很好地工作。

人们常说配置文件不应该使用非编程语言,因为它们需要由非编程人员编辑。但这种情况发生的频率是多少?人们真的期望非程序员改变复杂服务器端应用程序的事务隔离级别吗?非语言配置文件只在简单程度上工作良好。如果它们变得复杂,那么就应该考虑使用适当的编程语言。

目前我们在Java世界中看到的一件事是配置文件的不协调,其中每个组件都有自己的配置文件,这些文件与其他所有组件都不同。如果你使用这些组件中的一打,你可以很容易地得到一个DozzonConfiguration文件来保持同步。

我的建议是始终提供一种使用编程接口轻松进行所有配置的方法,然后将独立配置文件视为可选功能。您可以轻松构建配置文件处理以使用编程接口。如果您正在编写组件,则由用户决定是否使用编程接口,您的配置文件格式,或者编写自己的自定义配置文件格式并将其绑定到编程接口中

将配置与使用分离

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

如果这种分离在单个代码基中有用,当您使用诸如组件和服务之类的外来元素时,它是特别重要的。第一个问题是您是否希望将实现类的选择推迟到特定的部署。如果是这样,您需要使用插件的一些实现。一旦你使用了plugins,那么很重要的一点是插件的组装与应用程序的其余部分是分开的,这样你就可以很容易地为不同的部署替换不同的配置。如何做到这一点是次要的。此配置机制可以配置服务定位器,或者使用注入直接配置对象。


还有一些问题

在本文中,我集中讨论了使用依赖注入和服务定位器进行服务配置的基本问题,还有一些主题值得讨论。但我还没有时间去钻研。尤其是生命周期行为的问题。有些组件具有不同的生命周期事件:例如,停止和启动。另一个问题是,在这些容器中使用面向方面的思想的兴趣与日俱增。尽管我在开始的时候没有在文章中考虑过这些材料,我确实希望通过扩展本文或撰写另一篇文章来写更多关于这方面的内容。

您可以通过查看专门用于轻量级容器的网站来了解更多关于这些想法的信息。冲浪从微型集装箱春天网站将引导您更多地讨论这些问题,并从一些其他问题开始。


结论性思考

当前大量的轻量级容器都有一个共同的底层模式,即DependencyInjector模式。依赖注入是服务定位器的一种有用的替代方法。当构建应用程序类时,这两个类大致相等,但我认为服务定位器有一点优势,因为它的行为更直接。但是,如果您正在构建要在多个应用程序中使用的类,那么依赖注入是更好的选择。

如果您使用依赖注入,那么有许多样式可供选择。我建议您遵循构造函数注入,除非您遇到这种方法的一个特定问题,在这种情况下,切换到设定喷射。如果您选择建造或获得集装箱,寻找一个支持构造函数和setter注入的方法。

服务定位器和依赖注入之间的选择比将服务配置与应用程序中的服务使用分离的原则要简单得多。


分享:
如果你觉得这篇文章有用,请分享。我感谢你的反馈和鼓励

关于类似主题的文章…

…查看以下标签:

流行的 bet188足球 对象协作设计bet188足球 应用程序体系结构


致谢

我真诚地感谢在这篇文章中帮助过我的许多人。Rod JohnsonPaul HammantJoe Walnes阿斯拉克·海勒斯JonTirs_n和BillCaputo帮助我掌握了这些概念,并对本文的早期草稿进行了评论。Berin Loritsch和Hamilton Verissimo de Oliveira就如何适应阿瓦隆提供了一些非常有用的建议。DaveW-Smith一直在问关于我的初始接口注入配置代码的问题,因此让我不得不面对这样一个事实:它是愚蠢的。格瑞·洛瑞给我发了很多打字错误的修正,足以让我跨过感谢的门槛。

重要修改

2004年1月23日:重拨InterfaceInjection示例的配置代码。

2004年1月16日:添加了一个带有Avalon的定位器和投影的简短示例。

2004年1月14日:第一次出版