繁体   English   中英

了解DI框架的必要性

[英]Understanding the need for a DI framework

这可能是一个天真的问题。 . 我目前正在学习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");
    }
}

然后你可以简单地做:

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耦合 - 或者,如果你采取更现实的方案,为什么我的业务逻辑应该耦合到DAO层所需的JDBC连接实现?
我之后提出的解决方案是“然后将依赖项更进一步” - 好吧,所以现在我已将我的视图耦合到JDBC连接,我应该只处理HTML(或Swing,选择你的风格)。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

我通常不关心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();

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

不要忘记依赖注入的一个主要缺点:您无法使用强大的Java IDE的Find Usages轻松找出初始化内容的位置。 如果您经过多次重构并希望避免测试代码比应用程序代码大10倍,那么这可能是一个非常严重的问题。

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

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

最近,人们非常重视DI 框架 ,即使DI 模式被遗忘也是如此。 根据JB Rainsberger总结的 DI原则:

这很简单:通过要求协作者作为构造函数中的参数来显示依赖关系。 重复,直到您将有关要创建的对象的所有决策推送到入口点。 当然,这仅适用于服务(在DDD意义上)。 完成。

正如您所注意到的,手动配置依赖项与使用框架之间没有太大区别。 使用构造函数注入,如下所示,您的代码示例将具有更少的样板,并且编译器将强制您提供所有必需的依赖项(并且您的IDE可能会为您键入它们)。

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

DI框架可以减少创建工厂和将对象连接在一起的样板,但同时也可以更难找出每个依赖关系的来源 - DI框架的配置是挖掘的另一个抽象层通过,您的IDE可能无法告诉您从哪里调用特定的构造函数。

DI框架的间接缺点是它们可以使依赖关系过于简单 当您不再感到因拥有大量依赖项而感到痛苦时 ,您可能会继续添加更多依赖项,而不是重新考虑应用程序的设计以减少类之间的耦合。 手动连接依赖项 - 特别是在测试代码中 - 可以更容易地注意到,当您拥有太多依赖项时,测试设置变得更长并且编写单元测试变得更加困难。

DI框架的一些优点来自于它们对AOP等高级功能的支持(例如Spring的@Transactional),范围界定(尽管很多时候使用普通代码确定就足够了)和可插拔性(如果真的需要插件框架)。

最近我做了一个手动DI与基于框架的DI的实验。 进度和结果显示为Let's Code Dimdwarf剧集42到47中的截屏视频。 有问题的项目有一个基于Guice的插件系统,用于创建演员 ,然后我使用手动DI重写,没有Guice。 结果是更简单,更清晰的实现,只有更多的样板。

概要:首先尝试使用手动DI(最好是构造函数注入)。 如果有很多样板,请尝试重新考虑设计以减少依赖性。 如果某些功能为您提供了价值,请使用DI框架。

重要的是要理解Spring基本上是两件事,一件建立在另一件之上:

  1. 轻量级DI / IoC框架和实现它的类(例如XML应用程序上下文等);
  2. 它是一个轻量级的容器

(2)是Spring代码的主体。 基本上选择一种Java技术,你可能会发现Spring有它的辅助类。 这样你可以使用ActiveMQ,Sun One MQ或其他任何东西,并将它们抽象为Spring JmsTemplate,同样适用于数据访问技术,Web服务等。

所有这些助手都使用(1)将它们连接在一起。

使用依赖注入的最大好处之一是,在为该类创建单元测试时,可以更轻松地提供类依赖项的模拟或存根。 这允许您单独测试类,而不依赖于其协作者。

在您的示例中,无法在实例化实例化的类中模拟或存根Saw或SawMill。 如果Saw和SawMill是通过setter或构造函数设置的,那么你可以在运行单元测试时传入自己的模拟Saw并模拟SawMill。

Spring帮助您理解甚至鼓励DI模型。 但是,我不相信你必须拥有春天。

您可以使用Java配置文件,您可以调试,重构和执行代码分析。

我建议你将这些java配置文件放在另一个包中,但它们等同于XML配置文件。 如果这很重要,您甚至可以动态加载这些文件。

暂无
暂无

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

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