创建新对象与依赖注入之间的区别

35

创建新对象和依赖注入有什么区别?请详细解释。

6个回答

45

它们不完全可比。你总是需要通过实例化一个类来创建新对象。但依赖注入也需要创建新对象。

当你想要控制或验证被你使用或测试的类所使用的实例的行为时,依赖注入真正发挥作用。(对于测试驱动开发来说,除了最小的示例外,依赖注入是至关重要的)。

假设有一个需要一个Handle类对象的Holder类。传统的做法是让Holder实例自己创建并拥有它:

class Holder {
    private Handle myHandle = new Handle();
    public void handleIt() {
        handle.handleIt();
    }
}

Holder实例创建了myHandle,没有任何类外部的代码可以访问该实例。在某些情况下,例如单元测试,这是一个问题,因为无法在不创建Handle实例的情况下测试Holder类,而Handle实例可能又依赖于许多其他类和实例。这使得测试变得笨重和复杂。

通过注入Handle实例,例如在构造函数中,外部代码将负责创建实例。

class Holder {
    private Handle myHandle;

    public Holder(Handle injectedHandle) {
        myHandle = injectedHandle;
    }

    public void handleIt() {
        handle.handleIt();
    }
}

如你所见,代码几乎相同,而且Handle仍然是私有的,但Holder类现在与外部世界的耦合性更低,这使得许多事情变得更简单。并且在测试Holder类时,可以注入一个模拟或存根对象而不是实际实例,从而使验证或控制Holder、其调用程序和Handle之间的交互成为可能。

实际的注入将发生在其他地方,通常是一些“主要”程序。有多个框架可以帮助您在不编程的情况下完成此操作,但本质上这是“主要”程序中的代码:

...
private Handle myHandle = new Handle(); // Create the instance to inject
private Handler theHandler = new Handler(myHandle); // Inject the handle
...

本质上,依赖注入不过是一个高级版的set方法。当然,你可以使用它来实现注入机制,而不是像上面简单的示例一样在构造函数中实现。


我刚刚登录来点赞,这比被接受的答案好多了,而且解释得很透彻。 - Anearion
但是当我们创建一个Holder时,injectedHandle如何在我们的控制之外被初始化呢?如果有更多的参数,它如何知道如何初始化它们,或者根本不初始化它们? - Yoda
1
@Yoda,添加了如何进行实际注入的描述。希望这能有所帮助。 - thoni56
很棒的解释,希望能帮助许多正在学习设计模式的开发人员。 - Gonzdn

24

当然,两者都创建对象。不同之处在于谁负责创建。是需要其依赖项的类还是像Spring这样的容器,它连接组件的依赖项?您可以在单独的(通常是XML)配置文件中配置依赖关系。

这确实是一种关注点分离。类说我需要这个、这个和这个组件才能正常工作。类不关心如何获得其组件。您可以使用单独的配置文件将它们插入到类中。

举个例子,考虑一个需要支付模块的购物类。您不想硬编码将使用哪个付款模块。为了实现这一点,您反转了控制。您可以在容器的配置文件中用几个按键更改所使用的付款模块。优势在于您不需要修改任何Java代码。


1
这是一个很好的答案,因为我也在尝试理解当我们设计类时,如何决定是否将其注入为依赖项或在类内部创建对象的新实例。 - ILoveStackoverflow

21

创建一个新对象是非常明确的,你创建了一个所需类的新实例。

依赖注入是一种机制,为你提供需要的引用。想象一个表示连接到你的数据库的连接池的类 - 通常只有一个该类的实例。现在你需要将该引用分发到使用它的所有类中。这就是依赖注入派上用场的地方 - 通过使用Spring等DI框架,你可以定义一个连接池的实例将被注入到需要它的类中。

由于创建对象和依赖注入无法轻易地进行比较,因此你的问题本身并不容易回答...


4
那与使用 Singleton 有什么不同? - Radu
@Radu 单例模式是一种设计模式,它将特定类的实例数量限制为最多一个。因此,实例的创建是该模式实现的一部分。单例不会被注入到需要它的地方,这就是你需要依赖注入的原因。 - f1sh
3
我的问题是,相比于调用SingletonClass.getSingleInstance(),那个“神奇”实例化和分发的类的单个实例更好在哪里?如果只有一个实例,那么这种依赖注入对我来说似乎是多余的,只需使用单例模式即可。 - Radu
1
@Radu,您可以切换依赖注入提供的实例。例如,在单元测试中,您会获得一个专门用于单元测试的子类。此外,谁说被注入的实例是单例? - f1sh
2
“通常情况下,您只有一个该类的实例。”这是真的。我唯一看到的与单例模式相比的好处是,当不需要时,您可以垃圾回收该单个实例,并在需要时重新实例化它。 - Radu
@Radu 这个区别非常重要。SingletonClass.getSingleInstance() 返回一个具体的 SingletonClass 实例,你可以在代码的许多位置使用它。DI 通常会注入一个与 SingletonClass 签名匹配的接口 ISingletonClass。你可以在创建 DI 的单一位置确定使用哪个具体实现。因此,在单元测试或不同平台上,你可以重复使用代码而不改变它,使用不同的 ISingletonClass 实现。 - Holger Böhnke

5

依赖注入为您的应用程序增加了一层可配置性。当你硬编码对象构造时,你需要重新构建和重新部署你的应用程序,但是当你使用依赖注入时,你可以重新配置XML并改变行为而不需要重新构建和重新部署。有很多使用情况可以节省大量的时间和精力。


3
当使用控制反转容器进行依赖注入时,容器会创建对象,而不是开发人员。这样做是为了让容器能够将这些对象“注入”到其他对象中。

2
下面的问题的答案也可能给你想要的答案:为什么new操作符是一种反模式?简单地说,使用new操作符可能会在包含类中创建一个隐藏的、不可访问的依赖关系。这使得测试包含类更加困难,因为它涉及同时测试隐藏的依赖关系(当然,除了MOCK框架)。然而,你可以通过不使用new操作符并注入依赖对象来避免这种情况。这也有以下优点:
  • 为了测试目的,你可以注入不同的对象。
  • 由此产生的包含类更具可重用性,因为它可以支持不同实现的依赖对象。

迟到总比不到好,我希望能够来得及! - wjohnson

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