依赖注入容器有哪些好处?

105

我理解依赖注入本身的好处。以Spring为例,我也理解其他Spring功能的好处,如AOP、各种帮助程序等。我只是想知道XML配置的好处,例如:

<bean id="Mary" class="foo.bar.Female">
  <property name="age" value="23"/>
</bean>
<bean id="John" class="foo.bar.Male">
  <property name="girlfriend" ref="Mary"/>
</bean>

与纯Java代码相比,例如:

Female mary = new Female();
mary.setAge(23);
Male john = new Male();
john.setGirlfriend(mary);

哪种方式更容易调试,编译时检查并且可以被只懂Java的人理解呢?那么依赖注入框架的主要目的是什么呢?(或者展示其优点的代码片段。)


UPDATE:
对于...

IService myService;// ...
public void doSomething() {  
  myService.fetchData();
}
如果有多个myService的实现,IoC框架如何猜测我想注入哪个?如果只有一个给定接口的实现,并且我让IoC容器自动决定使用它,那么当第二个实现出现时,就会出现问题。如果有意只有一种可能的接口实现,那么就不需要注入它。
展示IoC的配置小片段,这将显示其好处。我已经使用Spring一段时间了,但我无法提供这样的示例。我可以展示单行代码,演示我使用的其他框架(如Hibernate,DWR等)的好处。
我意识到IoC配置可以在不重新编译的情况下更改。这真的是个好主意吗?我可以理解有人希望在不重新编译的情况下更改数据库凭据 - 他可能不是开发人员。在您的实践中,有多少人会更改IoC配置而不是开发人员?我认为对于开发人员来说,重新编译那个特定的类而不是更改配置并没有太大的工作量。对于非开发人员,您可能希望让他们的生活更轻松,并提供一些更简单的配置文件。
外部配置接口和它们的具体实现之间的映射是怎么回事?你不会使所有的代码都外部化,尽管你肯定可以 - 只需将它放在ClassName.java.txt文件中,手动读取和编译 - wow,你避免了重新编译。为什么要避免编译?
您提供映射声明后,节省编码时间,而不是在过程式代码中进行显式的映射。我理解有时候声明性方法可以节省时间,例如,我只在一个bean属性和一个DB列之间声明一次映射,然后Hibernate在加载、保存、基于HSQL建立SQL等时使用这个映射。这就是声明式方法起作用的地方。在Spring(我的例子中),声明需要更多行,并且与相应的代码具有相同的表现力。如果有一个示例可以使这种声明短于代码 - 我想看看。
控制反转原则允许轻松进行单元测试,因为您可以将真实实现替换为伪实现(如将SQL数据库替换为内存数据库)。我确实理解控制反转的好处(我更喜欢称所讨论的设计模式为依赖注入,因为IoC更通用 - 有许多种控制,我们只颠倒其中之一 - 初始化控制)。我问的是为什么有人需要使用其他东西来代替编程语言。我绝对可以使用代码将真实实现替换为伪实现。这段代码将表达与配置相同的内容 - 它只是使用伪值初始化字段。
mary = new FakeFemale();

我理解依赖注入(DI)的好处,但我不明白相比于配置代码而言,外部XML配置有何优势。我认为编译不应该被避免——我每天都进行编译,而且我还活着。我认为DI的配置是声明式方法的糟糕示例。如果声明只需声明一次并可以以多种方式使用,那么声明可以很有用,就像hibernate cfg一样,其中bean属性和DB列之间的映射用于保存、加载、构建搜索查询等等。Spring DI配置可以轻松地转换为配置代码,就像本问题开头所述的那样,难道不能吗?它只用于bean初始化,不是吗?这意味着在这里,声明式方法并没有增加任何东西,对吧?

当我声明hibernate映射时,我只是向hibernate提供一些信息,它就能基于此工作——我不会告诉它该做什么。在spring的情况下,我的声明告诉spring确切应该做什么——那么为什么要声明它,为什么不直接去做呢?


最近更新:
大家,很多回答告诉我依赖注入的好处,我知道依赖注入是好的。 问题是关于DI配置的目的,而不是初始化代码——我倾向于认为初始化代码更短、更清晰。 到目前为止我得到的唯一回答是,它避免了在配置更改时重新编译。我想我应该发布另一个问题,因为对于我来说,为什么在这种情况下应该避免编译还是个大谜团。


21
终于有人有勇气问这个问题了。确实,为什么你要避免重新编译,而以牺牲(或至少降低)工具/集成开发环境的支持为代价来改变你的实现呢? - Christian Klauser
3
标题似乎不太准确。作者说IOC容器还不错,但好像对使用XML配置而不是通过代码进行配置有意见(这也很合理)。我建议可能改为“通过XML或其他非代码方式配置IOC容器有哪些好处?” - Orion Edwards
我提供的@Orion示例(包括男性和女性)不需要任何IOC容器。我对IOC感到满意;无论是使用XML配置的容器还是其他方式,都仍然是一个未决问题。 - Pavel Feldman
@Orion 2:虽然我在大多数项目中都使用某种形式的IOC,但有些项目像变量分配容器或if语句容器一样从IOC容器中获益良多-只要语言足够好就可以了。我没有重新编译正在工作的项目,把开发/测试/生产初始化代码方便地分离出来的问题,对我来说这个标题是可以接受的。 - Pavel Feldman
我发现样例有问题。其中一个原则是注入服务,而不是数据 - Jacek Cz
16个回答

1
在.NET世界中,大多数IoC框架都提供XML和代码配置。
例如,StructureMap和Ninject使用流畅的接口来配置容器。您不再受限于使用XML配置文件。Spring也存在于.NET中,由于它是历史上的主要配置接口,因此 heavily relies on XML files,但仍然可以通过编程方式配置容器。

太好了,我终于可以使用代码来完成它本来就应该完成的任务了 :) 但是为什么我需要除编程语言以外的任何东西来做这些事情呢? - Pavel Feldman
我认为这是因为XML允许在运行时进行更改,或者至少可以进行配置更改而无需重新编译项目。 - Romain Verdier

1

易于将部分配置合并为最终完整配置。

例如,在Web应用程序中,模型、视图和控制器通常在单独的配置文件中指定。使用声明性方法,您可以加载,例如:

  UI-context.xml
  Model-context.xml
  Controller-context.xml

或者加载具有不同UI和一些额外控制器的配置:

  AlternateUI-context.xml
  Model-context.xml
  Controller-context.xml
  ControllerAdditions-context.xml

要在代码中执行相同的操作,需要基础设施来组合部分配置。虽然在代码中做到这一点并非不可能,但使用IoC框架肯定更容易。


0

在 XML 配置文件中进行初始化将简化您与已部署您的应用程序的客户端进行调试/适配工作的流程(因为它不需要重新编译+替换二进制文件)。


0

Spring也有一个属性加载器。我们使用这种方法来设置依赖于环境(例如开发、测试、验收、生产等)的变量。例如,这可能是要监听的队列。

如果没有理由使属性发生更改,那么也没有必要以这种方式进行配置。


0

你的情况非常简单,因此不需要像Spring这样的IoC(控制反转)容器。另一方面,当你“按接口编程而非实现”(这是面向对象编程中的良好实践)时,你可以编写如下代码:

IService myService;
// ...
public void doSomething() {
  myService.fetchData();
}

(请注意,myService的类型是IService - 一个接口,而不是具体实现)。当您有许多接口和许多实现时,让您的IoC容器在初始化期间自动提供正确的IService具体实例可能会很方便 - 手动完成可能会很麻烦。 IoC容器(依赖注入框架)的主要优点包括:

  • 外部配置接口与其具体实现之间的映射
  • IoC容器处理一些棘手的问题,如解决复杂的依赖关系图、管理组件的生命周期等。
  • 您可以声明性地提供映射,而不是在过程代码中编写代码,从而节省编码时间
  • 控制反转原则允许轻松进行单元测试,因为您可以使用虚拟实现替换真实实现(例如将SQL数据库替换为内存数据库)

-2

最吸引人的原因之一是“好莱坞原则”:不要打电话给我们,我们会打电话给你。一个组件不需要自己查找其他组件和服务;相反,它们会自动提供给它。在Java中,这意味着不再需要在组件内部进行JNDI查找。

单元测试一个组件也更容易:不需要给它实际需要的组件实现,而是使用(可能是自动生成的)mocks来模拟。


这个答案涉及到依赖注入。我知道它是什么,我知道它很好,并且我在问题的第一句话中清楚地表明了这一点。这个问题是关于DI配置的好处,与简单的初始化代码相比。 - Pavel Feldman
并没有真正回答这个问题。 - Casebash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接