了解DI框架的必要性

Knu*_*daa 39 java spring dependency-injection

这可能是一个天真的问题.我目前正在学习Spring框架和依赖注入.虽然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");
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以简单地做:

Saw saw = new HandSaw();
SawMill sawMill = new SawMill();
sawMill.setSaw(saw);
sawMill.run();
Run Code Online (Sandbox Code Playgroud)

这相当于:

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

<bean id="sawMill" class="SawMill">
   <property name="saw" ref="saw"/>
</bean>
Run Code Online (Sandbox Code Playgroud)

加:

ApplicationContext context = new ClassPathXmlApplicationContext("sawmill.xml");
SawMill springSawMill = (SawMill)context.getBean("sawMill");
springSawMill.run();
Run Code Online (Sandbox Code Playgroud)

当然,这是一个受到尊重的例子,对于更复杂的对象关系,存储XML文件可能比以编程方式编写文件更有效,但肯定必须有更多的东西呢?

(我知道Spring框架不止于此,但我正在考虑需要一个DI容器.)

在第一个例子中,在中途改变依赖关系也是微不足道的:

// gotta chop it faster
saw = new ChainSaw();
sawMill.setSaw(saw);
sawMill.run();
Run Code Online (Sandbox Code Playgroud)

Ran*_*ron 15

我有完全相同的问题,并且答案是这样的:
当然,你可以做你所描述的"然后你可以简单地做:......"(让我们称之为"A级").但是,这会将A类与HandSaw或SawMill类所需的所有依赖关系联系起来.为什么A应该与HandSaw耦合 - 或者,如果你采取更现实的方案,为什么我的业务逻辑应该耦合到DAO层所需的JDBC连接实现?
我之后提出的解决方案是"然后将依赖项更进一步" - 好吧,所以现在我已将我的视图耦合到JDBC连接,我应该只处理HTML(或Swing,选择你的风格).

由XML(或JavaConfig)配置的DI框架通过让您"获得所需的服务"来解决这个问题.你不关心它是如何初始化的,它需要什么工作 - 你只需获得服务对象并激活它.

此外,你对"加号:"(你所做的SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run();)有一个误解- 你不需要从上下文中获取sawMill bean - 应该将sawMill bean注入到你的对象(A类)中DI框架.所以不是...... getBean(...),而是去"sawMill.run()",而不是关心它来自哪里,谁初始化它以及如何.对于你所关心的一切,它可以直接进入/ dev/null,或测试输出,或真正的CnC引擎......关键是 - 你不在乎.所有你关心的都是你的小A级应该按照合同要求做的 - 激活锯木厂.

  • 只有在您开始测试之前才有效.然后你希望你有办法将"TestSawImpl"(只是断言)放入A而不是真正的"HandSaw",它需要一堆木头,一个电源和一个经过认证的锯操作员(可能还有一个医生待命) . (6认同)
  • 不,这总是如此.我可以使用模拟工具轻松编写单元测试.没有必要创建一个`TestSawImpl`,因为模拟工具允许我用一行代码模拟任何`Saw`实现类,即使在编译时不知道具体类. (4认同)
  • 根据我的理解,这个答案与依赖注入模式的"官方"定义(http://martinfowler.com/articles/injection.html)相矛盾.如果需要在运行时选择实现"Saw"抽象的实际类,则将类"A"耦合到类"HandSaw"是一个问题.否则,在客户端代码中直接实例化Saw实现类是完全可以的.在实际需要的情况下,DI实际上是"将配置与使用分离",而不是默认情况下. (2认同)

Apo*_*isp 15

依赖注入是隐式参数传递的简并形式,其目的基本相同,以解决所谓的配置问题:

配置问题是在整个程序中传播运行时首选项,允许多个并发配置集在静态保证分离下安全共存.

依赖注入框架弥补了隐含参数的缺乏,Curried函数以及语言中monad的便利设施.


duf*_*ymo 12

Spring有三个同样重要的功能:

  1. 依赖注入
  2. 面向方面的编程
  3. 框架类库,用于帮助持久化,远程处理,Web mvc等.

我同意,当你将它与一次调用new相比较时,很难看到依赖注入的优势.在这种情况下,后者肯定会看起来更简单,因为它只是一行代码.Spring的配置总是会增加代码行,所以它不是一个成功的论据.

当你可以从类中进行交叉关注并使用方面以声明方式设置它们时,它开始看起来好多了.与单个"新"调用的比较并不是Spring创建的.

也许使用Spring的最佳结果是它推荐的习惯用法使用接口,分层和DRY这样的好原则.这真的只是Rod Johnson在他的咨询演出中使用的面向对象最佳实践的升华.他发现随着时间的推移,他建立的代码帮助他为客户提供更好的软件.他总结了他在"Expert 1:1 J2EE"中的经验,最终将代码公开为Spring.

我想说,在你认为他的经验可以帮助你编写更好的代码的程度上买入框架.

在你将所有这三个功能结合起来之前,我认为你无法获得Spring的全部价值.


mat*_*t b 6

当然,这是一个受到尊重的例子,对于更复杂的对象关系,存储XML文件可能比以编程方式编写文件更有效,但肯定必须有更多的东西呢?

我认为将"接线"放在配置文件中而不是在代码中手动执行更有意义,原因如下:

  1. 配置在代码外部.
  2. 对连接的更改(告诉您sawmill使用不同的实例Saw)可以简单地对外部(XML)文件进行更改,不需要更改代码,重新编译,重新部署等.
  3. 当你有几十个类和几个注入层时(例如:你有一个web Controller类,它获取一个Service包含你的业务逻辑的类,它使用a DAOSaw从数据库中获取s,并将其DataSource注入其中,等等. ),手动连接协作者是繁琐的,需要几十行代码,除了连接之外什么都不做.
  4. 这有点不那么明确的"好处",但是通过在代码外部进行所有"连接",我认为它有助于向开发人员重新强调依赖注入的核心思想,特别是编码到接口,而不是实现.通过手动接线,可以很容易地回到原来的状态.


Jas*_*ers 5

我通常不关心XML或基于DI的DI,因为在我的用例中,它增加了不必要的复杂性.相反,我通常会选择某种形式的手动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();
Run Code Online (Sandbox Code Playgroud)

这意味着我仍然集中了耦合并具有其所有优点,而不依赖于更复杂的DI框架或XML配置文件.


Mne*_*nth 2

如果对插入的类进行硬编码,则需要该类在编译时可用。使用配置文件,您可以在运行时更改使用的锯(在您的情况下),而无需重新编译,甚至可以使用从刚刚放置在类路径中的新 jar 中获取的锯。如果值得,额外的复杂性取决于您必须解决的任务。