理解DI框架的必要性

9 浏览
0 Comments

理解DI框架的必要性

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

这相当于:



   

再加上:

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

诚然,这是一个假设的例子,对于更复杂的对象关系,将其存储在XML文件中可能比以编程方式编写更高效,但肯定还有更多需要考虑的地方吧?

(我知道Spring框架不仅仅是这样,但我在考虑DI容器的需求。)

在第一个例子中,在中途更改依赖关系也很简单:

// 必须更快地砍
saw = new ChainSaw();
sawMill.setSaw(saw);
sawMill.run();

0
0 Comments

Spring是一个非常重要的框架,它有三个同等重要的特性:依赖注入、面向切面编程和提供了一系列的框架类来帮助处理持久化、远程调用、Web MVC等问题。当我们将依赖注入与简单的一个new关键字进行比较时,很难看出依赖注入的优势。因为相比之下,使用new只需要一行代码,看起来更简单。而Spring的配置总是会增加代码的行数,所以这并不是一个胜出的论点。

但是当我们将一个横切关注点(cross-cutting concern)例如事务从我们的类中剥离出来,并使用面向切面编程的方式来设置它们时,情况就变得不一样了。与单个的"new"调用相比,Spring的优势就会显现出来。这也不是Spring被创建的初衷。

也许使用Spring最好的结果就是它推荐的编码范式使用了接口、分层和DRY等良好原则。它实际上是Rod Johnson在他的咨询工作中使用的面向对象最佳实践的精华。他发现,随着时间的推移,他建立的代码帮助他为客户交付更好的软件,他将自己的经验总结在了《Expert 1:1 J2EE》中,并将代码开源为Spring。

我想说的是,你应该根据自己的需求来选择使用该框架的程度。我认为只有将这三个特性结合起来,才能真正发挥Spring的价值。

0
0 Comments

理解对DI框架的需求

依赖注入是隐式参数传递的一种退化形式,其目的基本上是相同的,解决所谓的配置问题:

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

依赖注入框架弥补了语言中隐式参数、柯里化函数和方便的单子功能的不足。

0
0 Comments

理解DI框架的需求

在软件开发中,依赖注入(Dependency Injection,DI)是一个重要的设计模式,它可以解决类与类之间的耦合问题。通过使用DI框架,可以实现依赖的解耦和灵活性。然而,有些人对DI框架的需求产生了疑问,并提出了解决方案。

有人认为,在类A中直接实例化HandSaw或者从SawMill获取所有依赖项会导致类A与这些依赖项之间产生耦合。为什么类A需要与HandSaw耦合,或者更现实的情况是,为什么我的业务逻辑需要与DAO层所需的JDBC连接实现耦合呢?

为了解决这个问题,有人提出了将依赖项进一步移动的解决方案。通过使用DI框架,可以通过配置XML或JavaConfig来获取所需的服务。你不需要关心它是如何初始化的,它需要什么来工作,你只需要获取服务对象并激活它。

此外,对于“plus:”部分的理解也存在误区。你不需要从上下文中获取sawMill bean,sawMill bean应该已经被DI框架注入到你的对象(类A)中。所以,你只需要调用“sawMill.run()”,不需要关心它来自何处,由谁初始化,以及如何。对你来说,它可能直接进入/dev/null,或者是测试输出,或者是真实的CnC引擎...关键是,你不在乎。你只关心你那个小小的类A应该按照合同的要求去激活一个锯木厂。

然而,有人认为这个答案与依赖注入模式的“官方”定义相矛盾。只有在需要在运行时选择实现“Saw”抽象类的实际类时,类A与类HandSaw的耦合才是一个问题。否则,在客户端代码中直接实例化一个Saw实现类是完全可以接受的。DI的真正目的是在确实需要的情况下,“将配置与使用分离”,而不是默认情况下的任何地方。

然而,这个观点在测试时并不成立。在测试时,你希望能够将一个只进行断言的“TestSawImpl”放入A中,而不是一个需要一堆木材、电源和经过认证的锯操作员(可能还需要一个医生)的真实“HandSaw”。

有人认为,在单元测试中使用模拟工具可以轻松实现,不需要创建一个“TestSawImpl”。即使在编译时不知道具体的类,使用模拟工具也可以用一行代码模拟任何Saw实现类。

然而,引入另一层编织/动态类加载/其他技巧来使测试工作,使它们更加脱离实际模拟的现实世界。虽然这是可行的,但我更倾向于简单的解决方案-在测试中设置一个setter,以不同的方式进行设置。

因此,对于单元测试,你根本不使用任何模拟API吗?正如我所描述的,使用模拟工具并不比更“传统”的工具复杂(事实上恰恰相反)。

实际上,我更喜欢在可能的情况下提供自己的模拟。我尽量少使用“魔术”(编织/类加载器替换)。因为它往往会干扰产品的“魔术”。

好吧,如果你更喜欢手工模拟,那对我来说也没问题。然而,其他人认为使用模拟API是更简单的解决方案。

0