简体   繁体   English

了解DI框架的必要性

[英]Understanding the need for a DI framework

This might be a naive question. 这可能是一个天真的问题。 I'm currently learning the Spring framework and dependency injection . 我目前正在学习Spring框架和依赖注入 While the basic principle of DI is rather easy to grasp, it's not immediately obvious why you need an elaborate framework to implement it. 虽然DI的基本原理很容易掌握,但是为什么需要一个精心设计的框架来实现它并不是很明显。

Consider the following: 考虑以下:

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");
    }
}

Then you could simply do: 然后你可以简单地做:

Saw saw = new HandSaw();
SawMill sawMill = new SawMill();
sawMill.setSaw(saw);
sawMill.run();

Which would be equivalent to: 这相当于:

<bean id="saw" class="HandSaw"/>

<bean id="sawMill" class="SawMill">
   <property name="saw" ref="saw"/>
</bean>

plus: 加:

ApplicationContext context = new ClassPathXmlApplicationContext("sawmill.xml");
SawMill springSawMill = (SawMill)context.getBean("sawMill");
springSawMill.run();

Granted, this is a contrieved example, and with more complex object relationships it might be more efficient to stash up an XML file than writing it programmatically, but surely there must be more to it than that? 当然,这是一个受到尊重的例子,对于更复杂的对象关系,存储XML文件可能比以编程方式编写文件更有效,但肯定必须有更多的东西吗?

(I know the Spring framework is more than that, but I'm thinking of the need for a DI container.) (我知道Spring框架不止于此,但我正在考虑需要一个DI容器。)

In the first example it would also be trivial to change dependencies midstream: 在第一个例子中,在中途改变依赖关系也是微不足道的:

// gotta chop it faster
saw = new ChainSaw();
sawMill.setSaw(saw);
sawMill.run();

I've had the exact same question, and it was answered by this: 我有完全相同的问题,答案是这样的:
Granted, you could do what you've described in "Then you could simply do:..." (let's call that "class A"). 当然,你可以做你在“那么你可以简单地做......:”中所描述的内容(让我们称之为“A类”)。 However, that would couple class A to HandSaw, or to all dependencies needed from class SawMill. 但是,这会将A类与HandSaw或SawMill类所需的所有依赖关系联系起来。 Why should A be coupled to HandSaw - or, if you take a more realistic scenario, why should my business logic be coupled to the JDBC connection implementation needed for the DAO layer? 为什么A应该与HandSaw耦合 - 或者,如果你采取更现实的方案,为什么我的业务逻辑应该耦合到DAO层所需的JDBC连接实现?
The solution I proposed then was "then move the dependencies one step further" - ok, so now I've got my view coupled to JDBC connection, where I should only deal with HTML (or Swing, pick your flavor). 我之后提出的解决方案是“然后将依赖项更进一步” - 好吧,所以现在我已将我的视图耦合到JDBC连接,我应该只处理HTML(或Swing,选择你的风格)。

The DI framework, configured by an XML (or JavaConfig) solves this by letting you just "get the needed service". 由XML(或JavaConfig)配置的DI框架通过让您“获得所需的服务”来解决这个问题。 You don't care how it's initialized, what it needs to work - you just get the service object and activate it. 你不关心它是如何初始化的,它需要什么工作 - 你只需获得服务对象并激活它。

Also, you have a misconception there regarding the "plus:" (where you do SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run(); ) - you don't need to get the sawMill bean from the context - the sawMill bean should've been injected into your object (class A) by the DI framework. 另外,你对“plus:”(你在哪里做SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run(); )你有一个误解 - 你不需要得到sawMill bean从上下文中 - 应该通过DI框架将sawMill bean注入到您的对象(A类)中。 so instead of ...getBean(...), you'd just go "sawMill.run()", not caring where it came from, who initialized it and how. 所以不是...... getBean(...),而是去“sawMill.run()”,而不是关心它来自哪里,谁初始化它以及如何。 For all you care, it could go straight to /dev/null, or test output, or real CnC engine... The point is - you don't care. 对于你所关心的一切,它可以直接进入/ dev / null,或测试输出,或真正的CnC引擎......关键是 - 你不在乎。 All you care is your tiny little class A which should do what it's contracted to do - activate a saw mill. 所有你关心的都是你的小A级应该按照合同要求做的 - 激活锯木厂。

Dependency injection is a degenerate form of implicit parameter passing , and the purpose is essentially the same, to solve what's called The Configurations Problem : 依赖注入是隐式参数传递的简并形式,其目的基本相同,以解决所谓的配置问题

The configurations problem is to propagate run-time preferences throughout a program, allowing multiple concurrent configuration sets to coexist safely under statically guaranteed separation. 配置问题是在整个程序中传播运行时首选项,允许多个并发配置集在静态保证分离下安全共存。

Dependency Injection frameworks compensate for the lack of implicit parameters , Curried functions , and convenient facilities for monads in the language. 依赖注入框架弥补了隐含参数的缺乏, Curried函数以及语言中monad的便利设施。

Spring has three features that are equally important: Spring有三个同样重要的功能:

  1. dependency injection 依赖注入
  2. aspect oriented programming 面向方面的编程
  3. library of framework classes to help with persistence, remoting, web mvc, etc. 框架类库,用于帮助持久化,远程处理,Web mvc等。

I'll agree that it's hard to see an advantage to dependency injection when you compare that to a single call to new. 我同意,当你将它与一次调用new相比较时,很难看到依赖注入的优势。 In that case, the latter will certainly look simpler, because it's a single line of code. 在这种情况下,后者肯定会看起来更简单,因为它只是一行代码。 Spring's configuration will always increase the lines of code, so it's not a winning argument. Spring的配置总是会增加代码行,所以它不是一个成功的论据。

It starts looking a lot better when you can take a cross-cutting concern like transactions out of your classes and use aspects to set them up in a declarative way. 当你可以从类中进行交叉关注并使用方面以声明方式设置它们时,它开始看起来好多了。 The comparison to a single "new" call isn't what Spring was created for. 与单个“新”调用的比较并不是Spring创建的。

Perhaps the best outcome from using Spring is the way its recommended idiom uses interfaces, layering, and good principles like DRY. 也许使用Spring的最佳结果是它推荐的习惯用法使用接口,分层和DRY这样的好原则。 It's really just the distillation of object-oriented best practices that Rod Johnson used in his consulting gigs. 这真的只是Rod Johnson在他的咨询演出中使用的面向对象最佳实践的升华。 He found that the code he built up over time helped him make a buck delivering better software for his clients. 他发现随着时间的推移,他建立的代码帮助他为客户提供更好的软件。 He summarized his experience in "Expert 1:1 J2EE" and ended up open sourcing the code as Spring. 他总结了他在“Expert 1:1 J2EE”中的经验,最终将代码公开为Spring。

I'd say buy into the framework to the degree that you think his experience can help you write better code, too. 我想说,在你认为他的经验可以帮助你编写更好的代码的程度上买入框架。

I don't think you can get the full value of Spring until you combine all three of those features. 在你将所有这三个功能结合起来之前,我认为你无法获得Spring的全部价值。

Granted, this is a contrieved example, and with more complex object relationships it might be more efficient to stash up an XML file than writing it programmatically, but surely there must be more to it than that? 当然,这是一个受到尊重的例子,对于更复杂的对象关系,存储XML文件可能比以编程方式编写文件更有效,但肯定必须有更多的东西吗?

I think it makes more sense to put the "wiring up" in a configuration file rather than doing it manually in code for several reasons: 我认为将“接线”放在配置文件中而不是在代码中手动执行更有意义,原因如下:

  1. The configuration is external to your code. 配置在代码外部。
  2. Changes to the wiring up (to tell your sawmill to use a different instance of Saw ) can simply be made to the external (XML) file and do not require changing code, re-compiling, re-deploying, etc. 更改接线(告诉sawmill使用不同的Saw实例)可以简单地对外部(XML)文件进行更改,不需要更改代码,重新编译,重新部署等。
  3. When you have dozens of classes, and several layers of injection (for example: you have a web Controller class which gets a Service class which contains your business logic, which uses a DAO to obtain Saw s from the database, which gets a DataSource injected into it, etc.), manually wiring up the collaborators is tedious and requires a few dozen lines of code that do nothing but wiring up. 当你有几十个类和几个注入层时(例如:你有一个web Controller类,它获取一个包含你的业务逻辑的Service类,它使用DAO从数据库中获取Saw ,这将获得一个DataSource注入进入它等等,手动连接协作者是繁琐的,需要几十行代码,除了接线之外什么都不做。
  4. This is somewhat less of a clear-cut "benefit", but by having all of the 'wiring up' external to the code, I think it helps reenforce to developers the ideas that are at the core of dependency injection, specifically the idea of coding to the interface, not the implementation. 这有点不那么明确的“好处”,但是通过在代码外部进行所有“连接”,我认为它有助于向开发人员重新强调依赖注入的核心思想,特别是编码到接口,而不是实现。 With manual wiring up, it can be easy to slip back into old ways. 通过手动接线,可以很容易地回到原来的状态。

I generally don't care about XML or Reflection based DI because, in my use cases, it adds unnecessary complexity. 我通常不关心XML或基于DI的DI,因为在我的用例中,它增加了不必要的复杂性。 Instead, I usually go for some form of manual DI that, to me, seems more natural and has most of the benefits. 相反,我通常会选择某种形式的手动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();

This means I still centralize the coupling and have all the advantages of that, without the dependency on more complex DI framework or the XML configuration files. 这意味着我仍然集中了耦合并具有其所有优点,而不依赖于更复杂的DI框架或XML配置文件。

Don't forget one major disadvantage of dependency injection: you loose the ability to easily find out from where something is initialized using Find Usages of your powerful Java IDE. 不要忘记依赖注入的一个主要缺点:您无法使用强大的Java IDE的Find Usages轻松找出初始化内容的位置。 This might be a very serious point if you refactor a lot and want to avoid that the test code is 10 times bigger than the application code. 如果您经过多次重构并希望避免测试代码比应用程序代码大10倍,那么这可能是一个非常严重的问题。

大多数(如果不是全部)DI容器/库为您带来的一件事是拦截通过DI创建的所有实例的方法的可能性。

If you hard-code the inserted class, you need that class be available at compile-time. 如果对插入的类进行硬编码,则需要在编译时提供该类。 With a configuration-file you could change the used saw (in your case) at runtime without recompiling and even use a saw taken from a new jar you just placed in the classpath. 使用配置文件,您可以在运行时更改已使用的锯(在您的情况下)而无需重新编译,甚至可以使用从刚刚放置在类路径中的新jar中获取的锯。 If it is worth the extra complexity is dependent on the task you have to solve. 如果值得,额外的复杂性取决于您必须解决的任务。

Lately there has been very much emphasis on DI frameworks , even so that the DI pattern is being forgotten. 最近,人们非常重视DI 框架 ,即使DI 模式被遗忘也是如此。 DI's principles as summarized by JB Rainsberger : 根据JB Rainsberger总结的 DI原则:

It's simple: make dependencies explicit by requiring collaborators as parameters in the constructor. 这很简单:通过要求协作者作为构造函数中的参数来显示依赖关系。 Repeat until you've pushed all decisions about which objects to create into the entry point. 重复,直到您将有关要创建的对象的所有决策推送到入口点。 Of course, this only applies to Services (in the DDD sense). 当然,这仅适用于服务(在DDD意义上)。 Done. 完成。

As you've noticed, there is not much difference between configuring the dependencies manually vs. using a framework. 正如您所注意到的,手动配置依赖项与使用框架之间没有太大区别。 With constructor injection, as shown below, your code example would have even less boilerplate and the compiler would force you to provide all required dependencies (and your IDE will probably type them for you). 使用构造函数注入,如下所示,您的代码示例将具有更少的样板,并且编译器将强制您提供所有必需的依赖项(并且您的IDE可能会为您键入它们)。

SawMill sawMill = new SawMill(new HandSaw());
sawMill.run();

A DI framework can reduce the boilerplate of creating factories and wiring the objects together, but at the same time it can also make it harder to find out that where each dependency is coming from - the DI framework's configuration is one more layer of abstraction to dig through, and your IDE might not be able to tell you where a particular constructor is being called from. DI框架可以减少创建工厂和将对象连接在一起的样板,但同时也可以更难找出每个依赖关系的来源 - DI框架的配置是挖掘的另一个抽象层通过,您的IDE可能无法告诉您从哪里调用特定的构造函数。

An indirect disadvantage of DI frameworks is that they can make wiring the dependencies too easy . DI框架的间接缺点是它们可以使依赖关系过于简单 When you can't anymore feel pain from having lots of dependencies, you may just keep on adding more dependencies instead of rethinking the design of the application to reduce the coupling between the classes. 当您不再感到因拥有大量依赖项而感到痛苦时 ,您可能会继续添加更多依赖项,而不是重新考虑应用程序的设计以减少类之间的耦合。 Wiring the dependencies manually - especially in test code - makes it easier to notice when you have too many dependencies as the test setup becomes longer and it becomes harder to write unit tests. 手动连接依赖项 - 特别是在测试代码中 - 可以更容易地注意到,当您拥有太多依赖项时,测试设置变得更长并且编写单元测试变得更加困难。

Some advantages of DI frameworks come from their support of advanced features such as AOP (for example Spring's @Transactional), scoping (though many times scoping with plain code will be enough) and pluggability (if a plugin framework is really needed). DI框架的一些优点来自于它们对AOP等高级功能的支持(例如Spring的@Transactional),范围界定(尽管很多时候使用普通代码确定就足够了)和可插拔性(如果真的需要插件框架)。

Recently I made an experiment of manual DI vs. framework-based DI. 最近我做了一个手动DI与基于框架的DI的实验。 The progress and results are shown as screencasts in Let's Code Dimdwarf episodes 42 through 47 . 进度和结果显示为Let's Code Dimdwarf剧集42到47中的截屏视频。 The project in question had a Guice -based plugin system for creating actors , which I then rewrote using manual DI, without Guice. 有问题的项目有一个基于Guice的插件系统,用于创建演员 ,然后我使用手动DI重写,没有Guice。 The result was a much simpler and clearer implementation, and only a little bit more boilerplate. 结果是更简单,更清晰的实现,只有更多的样板。

Synopsis: First try using just manual DI (preferably constructor injection). 概要:首先尝试使用手动DI(最好是构造函数注入)。 If there becomes lots of boilerplate, try to rethink the design to reduce the dependencies. 如果有很多样板,请尝试重新考虑设计以减少依赖性。 Use a DI framework if some of its features provide value for you. 如果某些功能为您提供了价值,请使用DI框架。

It's important to understand that Spring is fundamentally two things, one built on top of the other: 重要的是要理解Spring基本上是两件事,一件建立在另一件之上:

  1. A lightweight DI/IoC framework and the classes to implement that (eg XML application contexts, etc); 轻量级DI / IoC框架和实现它的类(例如XML应用程序上下文等); and
  2. It is a lightweight container . 它是一个轻量级的容器

(2) is the bulk of the Spring code. (2)是Spring代码的主体。 Basically pick a Java technology and you'll probably find Spring has helper classes for it. 基本上选择一种Java技术,你可能会发现Spring有它的辅助类。 This is so you can use ActiveMQ, Sun One MQ or whatever and abstract them being a Spring JmsTemplate and the same goes for data access technologies, Web services, etc. 这样你可以使用ActiveMQ,Sun One MQ或其他任何东西,并将它们抽象为Spring JmsTemplate,同样适用于数据访问技术,Web服务等。

All of these helpers use (1) to wire them together. 所有这些助手都使用(1)将它们连接在一起。

One of the biggest benefits of using Dependency Injection is that it makes it much easier to provide mocks or stubs of a class's dependencies when creating a unit test for that class. 使用依赖注入的最大好处之一是,在为该类创建单元测试时,可以更轻松地提供类依赖项的模拟或存根。 This allows you to test the class in isolation without depending on its collaborators. 这允许您单独测试类,而不依赖于其协作者。

In your example, there is no way to mock or stub out Saw or SawMill in the class that where those are being instantiated. 在您的示例中,无法在实例化实例化的类中模拟或存根Saw或SawMill。 If Saw and SawMill were set via setters or the constructor, then you could pass in your own mock Saw and mock SawMill when running the unit test. 如果Saw和SawMill是通过setter或构造函数设置的,那么你可以在运行单元测试时传入自己的模拟Saw并模拟SawMill。

Spring helps you understand and even encourages DI models. Spring帮助您理解甚至鼓励DI模型。 However, I don't believe you have to have Spring. 但是,我不相信你必须拥有春天。

You can have configuration files in Java which you can debug, refactor and perform code analysis on. 您可以使用Java配置文件,您可以调试,重构和执行代码分析。

I suggest you put these java configuration files in another package, but they are otherwise equivalent to XML configuration files. 我建议你将这些java配置文件放在另一个包中,但它们等同于XML配置文件。 You can even load these file dynamically if that's important. 如果这很重要,您甚至可以动态加载这些文件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM