IoC 容器和Dependency Injection 模式.doc_第1页
IoC 容器和Dependency Injection 模式.doc_第2页
IoC 容器和Dependency Injection 模式.doc_第3页
IoC 容器和Dependency Injection 模式.doc_第4页
IoC 容器和Dependency Injection 模式.doc_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

IoC 容器和Dependency Injection 模式外文出处:/articles/injection.html译文:轻量级容器在Java社区近来可谓是风起云涌,这些容器能将来自不同项目的组件集结为一个内聚的应用程序。这些容器都是基于一个共同的模式,这个模式决定了容器如何完成组件装配,人们统称之为:“控制反转” Inversion of Control。本文将深入探讨这个模式的工作机制,并给它一个具体的名字:“依赖注入”Dependency Injection,并与服务定位器(Service Locator)模式进行比较。在二者中进行选择不重要,关键是一个二者都遵循的原则:应该将应用和配置分离。本文内容:1. 组件和服务2. 简单示例3. 控制反转4. 依赖注入的形式4.1使用PicoContainer进行构造器注入4.2使用Spring框架进行属性注入4.3接口注入5.使用服务定位器5.1为Locator使用分离接口5.2动态服务定位器5.3Avalon:Locator Injection双管齐下6.选择6.1服务定位 VS 依赖注入6.2构造器注入VS属性注入6.3代码配置还是配置文件6.4将配置从应用中分离7.更多话题8.结论在Java世界的企业级应用中有一个有趣的现象:很多人都在尝试做主流J2EE技术的替代品,这多出现在开源社区。这一方面很大程度上反映了主流J2EE技术的沉重和复杂,另一方面这其中诚然也有很多另辟蹊径极富创意。一个常见的问题就是如何将各种元素组织装配起来:Web控制层和数据接口由不同的团队开发而且团队间彼此知之甚少,你如何从中调整使其配合工作?很多框架都曾尝试这个问题,一些还在这个方向做了分支,致力于提供通用的各层组件组装解决方案。这些框架通常被称为轻量级容器,例如:PicoContainer Spring。这些容器的背后有一些有趣的设计原则做支持,这些原则是不拘泥于具体容器和Java平台的。这里我将对这些原则进行探索,例子是java的但我相信同我的大部分文章一样这些原则也适用于其它面向对象环境,特别是.NET。组件和服务将元素装配在一起,这个话题一开始就让我陷入棘手的术语问题:Servicecomponent对于这两个概念的定义,你轻而易举就能找到长篇大论但观点截然相反的文章。所以我先将二者的使用意图进行澄清。我这里的“component”组件是指一个软件单元,它可以使应用程序被使用但是不能被改变,组件的作者对这个应用程序没有控制权。不能修改我是指不能修改组件的源代码,但我们可以通过作者允许的方式对组件行为进行扩展。服务Service的概念和组件类似它是由外部应用程序调用。组件和服务主要的区别在于我认为是:组件是可以本地调用的(可以是jar包、程序集dll、或是源代码引入);服务则是通过远程接口进行同步或者异步调用的(比如web Service ,消息系统,RPC,socket)本文我将使用服务一词来代替,文中的多数逻辑也适用于本地组件。实际上,你往往需要一些本地框架以更好的访问远程服务。但是使用“组件或者服务”这样的说法太啰嗦了,拗口也难写,且“服务这个词当下更流行些。简单示例为了能让问题更具体一些,我通过一个例子来讨论这个话题。像我所有超简单的例子一样,这个例子简单的不真实,但希望它能够让你看清到底发生了什么而不至于纠缠于真实问题的种种细节。这个例子中我写了一个组件提供某位导演执导的电影列表。实现这个精彩功能只需要一个方法:/classMovieListerpublicMoviemoviesDirectedBy(Stringarg)ListallMovies=finder.findAll();for(Iteratorit=allMovies.iterator();it.hasNext();)Moviemovie=(Movie)it.next();if(!movie.getDirector().equals(arg)it.remove();return(Movie)allMovies.toArray(newMovieallMovies.size();这个功能的实现真的是简单至极了,它通过finder对象(后面会提及)来返回finder所能找到所有的电影列表,然后遍历列表,并返回特定导演的作品。很简单是吧,我不会去改进它,在本文它只是用来说明问题的,仅此而已。本文真正关心的是finder对象,或者说如何将lister对象和finder对象联系起来。这个问题有趣的原因在于我要期望那个漂亮的movieDirectedBy要独立于影片的存储方式。所以所有方法都会引用一个finder,finder对象可以完成findeAll的功能。我们可以把这个抽取出来做成接口:public interface MovieFinder List findAll();现在已经完成了很好的对象解耦,但是我需要用一个实体类来完成电影的查找工作时就要涉及到一个具体类;这里我把代码放在lister的构造器中:/class MovieLister. private MovieFinder finder; public MovieLister() finder = new ColonDelimitedMovieFinder(movies1.txt);这个实体类的名字就能表达这样一个事实:我们需要从一个逗号分隔的文本文件列表中获得影片列表。具体的实现细节我省略掉了,只要知道这是一种实现方式就可以了。如果这个类只是我自己用一点问题都没有。但是如果我的一位朋友也惊叹于这个精彩的功能并想使用它,那会怎样呢?如果他们也把影片列表存放在文本文件中并使用逗号分隔,而且把文件名改成“movies1.txt”那么一点问题也没有。如果仅仅是电影列表的名字不同那也没有问题,我可以从配置文件中读取。但是如果他们使用完全不同的存储介质呢?比如SQL数据库,xml文件,Web Service,哪怕只是另外一种格式规则存储的文本文件呢?这样我们就需要一个新的类来获取数据。由于已经抽取了一个MovieFinder接口,我可以不修改moviesDirectedBy 方法,我还是希望通过别的途径获得合适的movieFinder实现类的实例。图1:在MoiveLister类中简单创建MoiveFinder实例时的依赖关系上图展现了这种情况下的依赖关系:MovieLister类同时依赖于MoiveFinder接口及其实现。我们当然更期望MoiveLister只依赖于接口,但是我们如何得到一个获得一个MoiveFinder的实例呢?在我的企业级应用模式一书中,我们把这种情况称为插件Plugin:MoiveFinder不是在编译时就加入程序的,因为我不知道我的朋友会怎么用什么样的finder。我想让我的MoiveLister类能与任何MoiveFinder实现配合工作,并且允许在运行时加载完全不用我的控制。现在的问题就是如何设计这个连接使MoiveLister类在不知道实现类具体细节的前提下与其协作。将这种情况推广到真实系统中,我们或许有数十个服务和组件。每种情况我们都可以把使用组件的形式加以抽象,抽取接口并通过接口与组件进行交互(如果组件没有提供一个接口那么可 以通过适配暗度陈仓);但是我们希望用不同的方式部署系统,就需要使用插件方式来处理服务间的交互,这样我们才有可能在不同的部署方案中使用不同的实现。所以现在核心的问题就是我们如何将这些插件集结在一个应用程序中?这恰恰是新生代轻量级容器面对的主要障碍,而它们都无一例外的选择了控制反转。控制反转当这些容器的设计者谈话时会说这些容器是如此的有用,因为他们实现了“控制反转”。而我却深感迷惑,控制反转是框架的共有特征,如果说一个框架以实现了控制反转为特点相当于说我的汽车有轮子。问题是它们反转了什么?我第一次接触控制反转它关注的是对用户界面的控制。早期的用户界面全由程序控制。你会设计一系列的命令:类似于“请输入姓名”“请输入地址”你的程序会显示提示信息并取得用户响应。在图形化(甚至或者是基于触摸屏)用户界面中,用户界面框架会维护一个主循环,你的应用程序只需要为不同的区域设计事件和处理函数就可以了。这里程序的主要控制就发生了反转:从应用程序转移到了框架。因而对于新生代的容器,它们要反转的就是如何定位插件的具体实现。在我简单的例子中,MovieLister类负责MovieFinder的定位:它直接就实例化了一个子类。这样依赖,MoiveFinder也就不是插件了,因为它不是在运行时加载的。这些容器的方法是:只要插件遵守一定转化规则那么一个独立的程序集模块便可以注入到lister。结果就是我需要给这个模式一个更具体的名字。控制反转太宽泛了,因而常常让人迷惑。通过和一些IoC爱好者商讨之后我们命名为依赖注入。我将开始讨论各式各样的依赖注入,但是要先指出的一点是:要消解应用程序对插件的依赖,依赖注入绝不是不二法门。你也可以通过使用Service Locator模式做到这一点,讨论依赖注入之后我们会谈到Service Locator服务定位模式。依赖注入的形式依赖注入的基本思想是:有一个独立的对象-一个装配器,它获得实现了Finder接口合适的实现类并赋值给MovieLister类的一个字段。现在的依赖情况如下图所示:图2依赖注入器的依赖关系依赖注入的形式主要又三种:构造器注入,属性注入,接口注入。如果你关注最近关于依赖注入的讨论资料你就会发现这就是其中提到的类型1IoC 类型2IoC 类型3IoC。数字往往比较难记,所以我这里使用了名称命名。使用PicoContainer进行构造器注入首先我通过使用一个轻量级的容器PicoContainer来实现构造器注入。之所以要从这里开始是因为我在ThoughtWorks公司的几个同事在PicoContainer的开发社区很活跃,是的,可是说是一种偏爱。PicoContainer使用构造器来决定如何把一个finder的实现类注入到lister类中。要达到这个目的MoiveLister类必须要声明一个构造器而且要包含足够的注入信息。/class MovieLister.public MovieLister(MovieFinder finder) this.finder = finder;finder自身也是由PicoContainer管理的,所以文本文件的名字也可以通过容器注入来实现。/class ColonMovieFinder.public ColonMovieFinder(String filename) this.filename = filename;下面要做的就是告诉PicoContainer哪些接口和哪些类实现关联,哪些字符串注入到finder组件。private MutablePicoContainer configureContainer() MutablePicoContainer pico = new DefaultPicoContainer(); Parameter finderParams = new ConstantParameter(movies1.txt);pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);pico.registerComponentImplementation(MovieLister.class);return pico;这段配置代码通常位于另外一个类。我们这个例子,每一个使用我的lister类只要在自己的配置类编写适当的配置代码就可以了。当然通常的做法是把这些配置信息放在一个单独的配置文件中。你可以写一个类来读取配置文件来设置容器,尽管PicoContainer不包含这个功能。在另一个有紧密关系的项目NanoContainer 中提供了一些包装,允许你使用XML配置文件。NanoContainer 解析并对PicoContainer进行配置。这个项目的哲学理念是将配置形式与底层配置机制分离。使用这个容器你的代码大致会像这样:public void testWithPico() MutablePicoContainer pico = configureContainer(); MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class); Movie movies = lister.moviesDirectedBy(Sergio Leone); assertEquals(Once Upon a Time in the West, movies0.getTitle();尽管这里我使用了构造器注入,PicoContainer也支持属性注入,不过它的开发者推荐构造器注入。使用Spring框架进行属性注入Spring框架是一个应用广泛的企业级Java开发框架。它包含了对事务、持久化框架、Web应用开发和JDBC的功能抽象。和PicoContainer一样它也支持构造器注入和属性注入,但是它的开发者推荐是使用属性注入-用到这个例子里正合适。为了让我的Moive Lister能接收注入,我要给它添加一个赋值方法:/class MovieLister.private MovieFinder finder;public void setFinder(MovieFinder finder) this.finder = finder; 类似的我们为filename也添加这样一个方法/class ColonMovieFinder.public void setFilename(String filename) this.filename = filename;第三步就是设置配置文件。Spring支持多种配置方式,你可以通过XML文件配置或者通过代码,XML配置文件是比较理想的方式。movies1.txt测试代码大致是这样的:publicvoidtestWithSpring()throwsExceptionApplicationContextctx=newFileSystemXmlApplicationContext(spring.xml);MovieListerlister=(MovieLister)ctx.getBean(MovieLister);Moviemovies=lister.moviesDirectedBy(SergioLeone);assertEquals(OnceUponaTimeintheWest,movies0.getTitle();接口注入第三种注入技术是在接口中定义注入需要的信息并通过接口完成注入。 Avalon 就是使用了这种技术的框架的典型例子。稍后我会讨论更多相关内容,现在这个例子先用简单的代码来实现。使用这个技术我先要定义一个接口,并用这个完接口成注入。这个接口的作用就是把finder实例注入到实现了该接口的对象中。publicinterfaceInjectFindervoidinjectFinder(MovieFinderfinder);这个接口由提供MovieFinder的类提供。任何要使用MovieFinder的实体类都必须实现这个接口,比如lister。/classMovieListerimplementsInjectFinderpublicvoidinjectFinder(MovieFinderfinder)this.finder=finder;我使用类似的方法将文件名注入到finder实现中:publicinterfaceInjectFinderFilenamevoidinjectFilename(Stringfilename);/classColonMovieFinderimplementsMovieFinder,/InjectFinderFilenamepublicvoidinjectFilename(Stringfilename)this.filename=filename;接下来我还需要一些配置代码将这些组件的实现包装起来,简单起见我就在代码里完成了:/classTesterprivateContainercontainer;privatevoidconfigureContainer()container=newContainer();registerComponents();registerInjectors();container.start();配置分成了两个阶段,通过定位关键字来注册组件,这和其它的例子一样:/classTesterprivatevoidregisterComponents()container.registerComponent(MovieLister,MovieLister.class);container.registerComponent(MovieFinder,ColonMovieFinder.class);下一步就是注册要依赖组件的注入器,每一个注入接口都需要一些代码来注入到依赖的对象。这里我使用容器来完成注入器的注册。每一个注入器对象都实现了注入接口。classTesterprivatevoidregisterInjectors()container.registerInjector(InjectFinder.class,container.lookup(MovieFinder);container.registerInjector(InjectFinderFilename.class,newFinderFilenameInjector();publicinterfaceInjectorpublicvoidinject(Objecttarget);当依赖设计成是一个为容器而写的类那么它对组件自身实现接口注入是有意义的,就像这里我对moivefinder做的修改一样。对于普通类,比如string,我使用内部类来完成配置代码。/classColonMovieFinderimplementsInjectorpublicvoidinject(Objecttarget)(InjectFinder)target).injectFinder(this);/classTesterpublicstaticclassFinderFilenameInjectorimplementsInjectorpublicvoidinject(Objecttarget)(InjectFinderFilename)target).injectFilename(movies1.txt);/Theteststhenusethecontainer.classIfaceTesterpublicvoidtestIface()configureContainer();MovieListerlister=(MovieLister)container.lookup(MovieLister);Moviemovies=lister.moviesDirectedBy(SergioLeone);assertEquals(OnceUponaTimeintheWest,movies0.getTitle();容器使用声明了注入器的接口来指明注入器和实体类之间的依赖关系。(具体用什么容器实现不重要,我也不再作展示了。这只会让你笑我)使用服务定位器依赖注入的关键优势是它消除了lister类对具体finder类实现的依赖。这样就可以让我的朋友方便的将一个特定的实现插入到他们的应用环境中了。注入绝不是唯一出路,另外一个方案就是使用Service Locator:服务定位器模式。服务定位器的基本思想是一个对象知道如何获得应用程序所需要的所有服务。在我们的例子里服务定位器就应该有一个方法返回所需的finder实例。这只是减少了一点负担,从下图的依赖关系可以看出我们还是需要在lister中获得定位器。图3服务定位器的依赖关系这里我把服务定位器做成一个单件注册表。lister可以在实例化时通过服务定位器获取一个finder的实例。class MovieLister. MovieFinder finder = ServiceLocator.movieFinder();class ServiceLocator.public static MovieFinder movieFinder() return soleInstance.movieFinder;private static ServiceLocator soleInstance;private MovieFinder movieFinder;和注入一样我们也必须配置服务定位器。这里我还是通过代码实现,要是通过读取配置文件的进行配置也非难事。class Tester.private void configure() ServiceLocator.load(new ServiceLocator(new ColonMovieFinder(movies1.txt); class ServiceLocator.public static void load(ServiceLocator arg) soleInstance = arg; public ServiceLocator(MovieFinder movieFinder) this.movieFinder = movieFinder; Heres the test code.class Tester.public void testSimple() configure(); MovieLister lister = new MovieLister(); Movie movies = lister.moviesDirectedBy(Sergio Leone); assertEquals(Once Upon a Time in the West, movies0.getTitle(); 我常听到这样的抱怨服务定位器不是什么好东西因为你无法替换服务定位器返回的实例也就无法进行测试。当然要是你设计很糟糕遇到了这些麻烦在所难免。在这个例子服务定位器就是一个简单的数据容器,可以简单修改就可以创建适用于测试的服务。对于更复杂的情况我可以从服务定位器派生子类并将子类传递给注册表类变量。另外我可以让服务定位器暴露出来一个静态方法而不是直接访问该类实例的变量。我还可以使用特定的线程存储提供特定线程的服务定位器。所有这些变化都不会修改使用服务定位器的Client.一种对服务定位器的改进思路是不设计成单件形式。单件的确是实现注册表的简单方法,但这这只是实现决定很容易改变它。为定位器使用分离接口上面的简单方案有一个问题,lister类依赖于整个服务定位器哪怕它仅仅使用一个服务。我们可以通过使用分离的接口来改变这种对服务定位器的依赖,lister使用的是它想要使用的那部分接口。相应的lister类的提供者也应该提供这样一个定位器接口,使用者可以通过这个接口获得finder。public interface MovieFinderLocator public MovieFinder movieFinder();The locator then needs to implement this interface to provide access to a finder.MovieFinderLocator locator = ServiceLocator.locator();MovieFinder finder = locator.movieFinder(); public static ServiceLocator locator() return soleInstance;public MovieFinder movieFinder() return movieFinder;private static ServiceLocator soleInstance;private MovieFinder movieFinder;注意这里由于使用了接口我们就不能再使用静态方法直接访问服务,我们必须在类中获得定位器实例进而使用各种需要的服务。动态服务定位器上面的例子是静态的,服务定位器拥有你想要的各种服务。这还不是唯一的路子,你可以使用动态服务定位器,它允许你在其中注册需要的任何服务并在运行时按需获取服务。下面的例子中,服务定位器使用map来保存服务信息,而不是放在字段中,并提供了一个通用的方法来获取服务。class ServiceLocator.private static ServiceLocator soleInstance; public static void load(ServiceLocator arg) soleInstance = arg; private Map services = new HashMap(); public static Object getService(String key) return soleInstance.services.get(key); public void loadService (String key, Object service) services.put(key, service); 配置的功能就是通过特定的关键字来加载服务class Tester.private void configure() ServiceLocator locator = new ServiceLocator(); locator.loadService(MovieFinder, new ColonMovieFinder(movies1.txt); ServiceLocator.load(locator); /通过同样的关键字使用服务/class MovieLister.MovieFinder finder = (MovieFinder) ServiceLocator.getService(MovieFinder);总体上讲我不喜欢这个方法,尽管它的确灵活但是它不够直观明了。我只有通过字符串关键字才能定位一个服务,我更倾向比较明了的方法因为通过看接口定义就可以很清楚的知道如何获得一个特定的服务。Avalon:Locator Injection双管齐下依赖注入和服务定位器不是水火不容的概念。Avalon框架就是一个把二者和谐使用的绝好例证。Avalon使用服务定位器但是同时使用注入来告诉组件到那里找定位器。/Berin Loritsch给我发了一个简单的可以运行的例子,就是用Avalon实现的 public class MyMovieLister implements MovieLister, Serviceable private MovieFinder finder;public void service( ServiceManager manager ) throws ServiceException finder = (MovieFinder)manager.lookup(finder);Service方法就是方法注入的例子,它可以是容器将一个ServiceManager对象注入到Lister对象中。而ServiceMananger则是一个服务定位器的例子。这个例子中,MyMoiveLister并不是要把ServiceManager保存在字段里面,而是借助它获得finder的实例,并将finder保存起来。选择到目前为止我已经集中阐述了我对这两个模式及其变化形式的看法。下面就要细数它们的优缺点了,这样有助于判断什么时候用哪一个。服务定位器VS依赖注入首先是服务定位器和依赖注入的 选择。第一点要注意的虽然上面两个过于简单的例子没有表现出来实际上这两种模式都提供了基本的解耦功能。两种情况下应用程序代码都不依赖于具体服务接口的实现。二者的区别在于怎样把具体的实现提供给应用程序类。使用服务定位器是通过应用程序发送消息明确要哪一个服务的实现。使用依赖注入不明确发送请求,服务的实现会自然的出现在应用程序的类中,这就是控制反转。控制反转是所有框架的共同特征,但是你要付出一点代价。它会难于理解并在调试时带来一些问题。所以整体来讲我尽量避免使用它。这并不是说它不好,只是我更倾向于一个直观的方案。二者关键的区别在于:使用服务定位器每一个服务的使用者都会依赖于服务定位器。定位器隐藏了使用者对其它服务具体实现的依赖,而必须依赖服务定位器本身。所以选择注入还是定位器就在于你是否在意对定位器的依赖!使用依赖注入你可以很清楚的看到组件之间的依赖关系。你只需要观察使用了什么样的依赖注入机制,比如构造器注入,就可以看清其中的依赖关系。而在服务定位器中你必须在源代码中搜索对服务定位器的调用,具备引用分析功能的IDE可以简化这个操作,但还不如直接观察构造器和属性简单。关于这二者的选择很大程度上取决于我们服务使用者的性质。如果你的应用程序中有不同的类使用同一个服务,那么应用程序使用服务构造器就不是什么大的问题。上面影片列表的例子使用服务定位器就很好。我的朋友只需要对定位器做一点配置(配置代码或者配置文件均可)就可以获得适当的服务实现。而在这种应用场景中,注入器的反转没有什么引人注目的地方。要是把lister看成一个组件提供给别的应用程序使用情况就不同了。这种情况下我不知道使用者会使用什么样的服务定位器API, 每一个使用者都有可能都使用自己的服务定位器,而这些服务定位器彼此之间并不兼容。我可以使用分离接口的方法来解决这个问题,每一个使用者都可以开发自己的服务定位器的适配器。但即使是这样我还是希望第一个服务定位器中寻找特定的接口。而且一旦适配器出现,定位器的简单直接就开始削弱了。使用注入器不会产生组件和注入器之间的依赖关系,可是注入器一旦配置确定了,组件也无法获得更多的服务。人们倾向选择依赖注入的原因是进行测试更容易了。关键之处是为了进行测试你可以使用真实服务或者使用Stubs桩或者mocks方法等创建的测试用服务。但是实际上这里使用依赖注入和服务定位器是没有区别的:两者都很好的支持“桩”。我猜想之所以有这么多人有依赖注入更容易进行测试的印象,是因为他们并没有努力做到定位器可替换。这就是持续测试的意义所在,如果你不能很容易的使用“桩”进行测试,这就意味着你的设计出现了严重问题。当然,如果组件环境有很强的侵入性那么测试的问题会更加严重,例如Java EJB框架。我的观点是这些框架应该尽量减小框架本身对应用程序代码的影响,特别是不要做那些可能会让编辑-执行过程变慢的操作。用插件来取代重量级的组件会对测试过程有很大帮助。这也是TDD-测试驱动开发实践的关键。所以,主要的问题还是在于作者是否愿意组件脱离自己控制被应用到其它程序中。如果作者是这样想的,那么它就不能对服务定位器做任何假设-哪怕是最小的假设也会带来麻烦。构造器注入vs属性注入服务整合的时候,你要遵循一定规则来将所有的东西整合在一起。依赖注入的优点在于:只要非常简单的约定-至少对构造器注入和属性注入来说是这样的。你不必在你的组件中做什么特殊的处理就是直接做一下配置就可以了。接口注入的侵入性要强的多,因为你要按照需要写很多接口。用容器提供一个小型的接口集合,这个主意不错,Avalon就是这么做的。整合和依赖方面还有很多工作要做,这就是为什么很多轻量级容器依然致力于研究属性注入和构造器注入的原因。属性注入和构造器注入反映了一个有趣的问题,所有面向对象系统都会遇到:应该在哪里填充对象字段?构造器还是属性?一直以来我的实践都是尽量把对象的创建放在构造阶段,也就是构造器中填充对象字段。这个建议可以追溯到Kent Becks Smalltalk Best Practice Patterns: Constructor Method and Constructor Parameter Method.带参构造器可以明确创建意图,如果不知一种构造方式,就要提供多个构造函数。构造器的另外一个好处就是允许你隐藏一些不变字段只要没有setter就可以了。我认为这个很重要如果一个字段被认为是不应该被改变的,那么属性赋值锁定就能很好的表明这一点。如果你使用属性初始化那就痛苦了,实际上我倾向于避免使用设置方法,而是使用哪个类似于initFoo之类的方法,来强调这个方法应该在对象创建之初进行调用。不过什么事情都有个例外,如果构造函数的参数过多就显得有点混乱了,特别是对于那些不支持关键字参数的语言更是如此。一个冗长的参数列表说明这个类过于复杂了,应该进行拆分,确实需要很多参数的情况除外。如果有多种方式构造一个有效的对象,很难通过构造函数的个数和类型来描述构造意图。这就是Factory Method模式的用武之地了,工厂方法可以借助多个私有构造函数和Setter方法组合来完成自己的任务。经典Factory Method模式的问题就在于它往往通过静态方法的形式实现,你无法在接口声明它们。你可以创建一个工厂类,但是那又变成另一个服务实体类了。工厂服务是一种不错的技巧,但是你需要某种方式实例化工厂对象,问题还在。如果要传入像字符串之类的简单类型,构造器注入也会有问题:使用属性注入你可以在属性方法的名字中说明参数的用途;而构造器注入你只能靠参数的位置决定每一个参数的作用,而这实践起来难很多。如果你的对象又有很多构造器并且对象间存在继承关系,事情就会变糟糕了。为了能够正确初始化,你必须将子类的构造器调用转到父类构造器然后处理自己的参数。这可能导致构造器爆炸式膨胀。尽管诸多缺陷我还是首推构造器注入,不过我们已经做好准备,一旦遇到上述的问题就转向属性注入。在以依赖注入作为框架核心的几个开发团队之间,属性注入还是构造器注入这个话题上争论不休

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论