了解DI框架的需求
这可能是一个天真的问题。 我目前正在学习Spring框架和dependency injection 。 虽然DI的基本原理很容易理解,但为什么需要一个精细的框架来实现呢?
考虑以下:
public abstract class Saw { public abstract void cut(String wood); } public class HandSaw extends Saw { public void cut(String wood) { // chop it up } } public class ChainSaw extends Saw { public void cut(String wood) { // chop it a lot faster } } public class SawMill { private Saw saw; public void setSaw(Saw saw) { this.saw = saw; } public void run(String wood) { saw.cut("some wood"); } }
那么你可以简单地做:
Saw saw = new HandSaw(); SawMill sawMill = new SawMill(); sawMill.setSaw(saw); sawMill.run();
这将相当于:
<bean id="saw" class="HandSaw"/> <bean id="sawMill" class="SawMill"> <property name="saw" ref="saw"/> </bean>
加:
ApplicationContext context = new ClassPathXmlApplicationContext("sawmill.xml"); SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run();
当然,这是一个调查的例子,对于更复杂的对象关系来说,存储XML文件比编写程序更有效,但是肯定还有比这更多的东西吗?
(我知道Spring框架不止于此,但我正在考虑需要一个DI容器。)
在第一个例子中,在中途改变依赖关系也是微不足道的:
// gotta chop it faster saw = new ChainSaw(); sawMill.setSaw(saw); sawMill.run();
我有完全相同的问题,并回答了这个问题:
当然,你可以做你在“那么你可以简单地做:…”(我们称之为“A类”)。 但是,这将把A类与HandSaw,或者类SawMill所需的所有依赖关系结合起来。 为什么要将A与HandSaw耦合?或者,如果您采取更现实的scheme,为什么我的业务逻辑应与DAO层所需的JDBC连接实现耦合?
那么我提出的解决scheme是“然后将依赖关系进一步移动” – 好吧,现在我已经把我的视图耦合到了JDBC连接,我只能处理HTML(或Swing,挑选你的风格)。
由XML(或JavaConfig)configuration的DI框架通过让您“获得所需的服务”来解决这个问题。 你不关心它是如何初始化的,它需要什么工作 – 你只需要获取服务对象并激活它。
另外,你还有一个关于“plus:”的错误概念(你在哪里做SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run();
) – 你不需要获取sawMill bean从上下文来看,sawMill bean应该已经被DI框架注入到你的对象(类A)中。 所以,而不是… getBean(…),你只要去“sawMill.run()”,不关心它来自哪里,谁初始化它,以及如何。 对于你所关心的,它可以直接到/ dev / null,或testing输出,或真正的CnC引擎…重点是 – 你不在乎。 你所关心的只是你的小小Aclass,它应该按照合同要求来做 – 激活一个锯木厂。
dependency injection是一种隐式parameter passing的退化forms,目的本质上是相同的,即解决所谓的“configuration问题 :
configuration问题是在整个程序中传播运行时首选项,允许多个并发configuration集在静态保证的分离下安全地共存。
dependency injection框架弥补了语言中缺less隐式参数 , Curried函数和monad方便的function 。
Spring有三个同样重要的特性:
- dependency injection
- 面向方面的编程
- 框架类库,以帮助持久性,远程处理,networkingmvc等
我会同意,当你比较一个新的调用时很难看到dependency injection的好处。 在这种情况下,后者肯定会看起来更简单,因为它是一行代码。 Spring的configuration永远会增加代码行,所以这不是一个胜利的论点。
当你可以像你的类中的交易一样进行跨领域的关注并使用方面以声明的方式进行设置时,它开始看起来好多了。 与一个“新”电话的比较不是Spring创build的。
也许使用Spring的最好结果是推荐的习惯用法使用接口,分层和像DRY这样的好原则。 Rod Johnson在他的咨询演出中使用的是面向对象的最佳实践。 他发现,随着时间的推移,他build立的代码帮助他为自己的客户提供更好的软件。 他总结了他在“专家1:1 J2EE”中的经验,并最终以Spring开源代码。
我想说,购买框架的程度,你认为他的经验可以帮助你写更好的代码。
我不认为你可以得到spring的全部价值,直到你结合所有这三个function。
当然,这是一个调查的例子,对于更复杂的对象关系来说,存储XML文件比编写程序更有效,但是肯定还有比这更多的东西吗?
我认为将配线文件放在configuration文件中比在代码中手动configuration更有意义,原因如下:
- 该configuration是您的代码的外部。
- 可以简单地将外部(XML)文件更改为接线(告诉
sawmill
使用不同的Saw
实例),不需要更改代码,重新编译,重新部署等。 - 当你有几十个类和几个注入层时(例如:你有一个Web
Controller
类,它得到一个包含你的业务逻辑的Service
类,它使用DAO
从数据库中获取Saw
,从而得到一个DataSource
注入等等),手动配合协作者是很繁琐的,并且需要几十行代码,除了接线以外什么也不做。 - 这不是一个明确的“好处”,而是通过让代码外部的所有“接线”,我认为这有助于向开发人员重新强调dependency injection核心思想,特别是编码到接口,而不是执行。 手动接线,可以很容易地回到旧的方式。
我通常不关心基于XML或reflection的DI,因为在我的用例中,它增加了不必要的复杂性。 相反,我通常会采取某种forms的手动DI,对我来说,似乎更自然,并且具有大部分好处。
public class SawDI{ public Saw CreateSaw(){ return new HandSaw(); } public SawMill CreateSawMill(){ SawMill mill = new SawMill(); mill.SetSaw(CreateSaw()); return mill; } } // later on SawDI di = new SawDI(); SawMill mill = di.CreateSawMill();
这意味着我仍然集中了耦合,并具有所有优点,而不依赖于更复杂的DI框架或XMLconfiguration文件。
大多数(如果不是全部的话)DI容器/库带给你的一件事是拦截通过DI创build的所有实例的方法的可能性。
不要忘记dependency injection的一个主要缺点:使用强大的Java IDE的查找用法,你很容易发现从哪里初始化的东西。 这可能是一个非常严重的问题,如果你重构很多,并且要避免testing代码比应用程序代码大10倍。
如果对插入的类进行硬编码,则需要在编译时提供该类。 使用configuration文件,您可以在运行时更改使用的锯(在您的情况下),而无需重新编译,甚至可以使用从刚刚放置在类path中的新锯中取锯。 如果这是值得的额外的复杂性取决于你必须解决的任务。
最近非常强调DI 框架 ,即使DI 模式被遗忘。 DI的原则由JB Rainsberger总结 :
很简单:通过在构造函数中要求协作者作为参数来明确依赖关系。 重复,直到你把关于创build哪个对象的所有决定推到入口点。 当然,这只适用于服务(在DDD的意义上)。 完成。
正如你已经注意到的那样,手动configuration依赖与使用框架没有多大区别。 使用构造函数注入(如下所示),代码示例的样板文件更less,编译器将强制您提供所有必需的依赖项(并且您的IDE可能会为您input它们)。
SawMill sawMill = new SawMill(new HandSaw()); sawMill.run();
DI框架可以减less创build工厂的样板并将对象连接在一起,但同时也会使得难以发现每个依赖关系是从哪里来的 – DI框架的configuration是一个抽象的层次来挖掘通过,您的IDE可能无法告诉你在哪里调用特定的构造函数。
DI框架的一个间接的缺点是他们可以很容易地连接依赖关系。 当你再也不会因为拥有大量的依赖而感到痛苦的时候,你可能会继续增加更多的依赖,而不是重新思考应用程序的devise,以减less类之间的耦合。 手动连接依赖关系(特别是在testing代码中)使得在testing设置变得更长时,如果依赖关系过多,则更易于注意到,编写unit testing变得更加困难。
DI框架的一些优点来自于对AOP(例如Spring的@Transactional)等高级特性的支持,范围界定(尽pipe许多时候使用普通代码进行范围界定就足够了)和可插拔性(如果真的需要插件框架的话)。
最近我做了一个手动DI与基于框架的DI的实验。 进度和结果显示为Let's Code Dimdwarf第42至47集的截屏。 有问题的项目有一个基于Guice的插件系统来创build演员 ,然后我使用手动DI重写,没有Guice。 结果是一个更简单和更清晰的实现,只有一点点的样板。
简介:首先尝试使用手动DI(最好是构造函数注入)。 如果有大量的样板文件,请尝试重新考虑devise以减less依赖关系。 如果某些function为您提供了价值,请使用DI框架。
重要的是要明白,spring基本上是两件事情,一个build立在另一个之上:
- 一个轻量级的DI / IoC框架和实现它的类(例如XML应用程序上下文等); 和
- 这是一个轻量级的容器 。
(2)是Spring代码的大部分。 基本上select一个Java技术,你可能会发现Spring有助手类。 这样你就可以使用ActiveMQ,Sun ONE MQ或其他任何东西,并将它们抽象为Spring JmsTemplate,对于数据访问技术,Web服务等也是如此。
所有这些助手都使用(1)将它们连接在一起。
使用dependency injection最大的好处之一就是在为这个类创build一个unit testing时,提供一个类的依赖关系的模拟或者存根更容易。 这使您可以独立testing该类,而不依赖于其协作者。
在你的例子中,没有办法在那些实例化的类中模拟或者删除Saw或者SawMill。 如果Saw和SawMill是通过setter或构造函数设置的,那么在运行unit testing时,可以传入自己的模拟锯和模拟SawMill。
spring可以帮助你理解甚至鼓励DI模型。 不过,我不相信你必须有spring。
您可以使用Java中的configuration文件,您可以对其进行debugging,重构和执行代码分析。
我build议你把这些javaconfiguration文件放在另一个包中,但是它们在其他方面等同于XMLconfiguration文件。 如果这很重要,你甚至可以dynamic地加载这些文件。