依赖注入是一种模式吗,这就是它吗?

14

我与同事就依赖注入进行了激烈辩论,发现我并不完全了解该主题的所有细节。

于是,看看这段代码(只是为了让你知道,我们正在使用Castle Windsor)。

IPlayerService service = Container.Resolve<IPlayerService>();

上述代码显然是使用IoC实现DI的示例。

但请看下面的代码 (更新: 假设我通过构造函数传递了所有外部依赖项):

var playerClient = new PlayerClient();
var playerSkinClient = new PlayerSkinClient();
IPlayerService service = new PlayerService(playerClient, playerSkinClient);

我认为上面的代码是依赖注入(DI)模式的一个例子,并且DI可以在没有IoC的情况下存在。

现在,我的同事并不完全反对我的观点,但他说上述代码不是涉及DI的任何模式的例子。

  1. 那么,DI能否作为一种仅使用模式而没有额外框架的方式?

  2. 如果可以,上面的代码是否是它的一个例子?

  3. 最后,如果没有容器的概念,是否存在定义DI模式的方式?

更新

我将会更仔细地阅读答案和评论,但是我想感谢大家迄今为止给出的深思熟虑的答案和评论!


1
在我看来,你的观点是完全正确的 - 这是最重要的事情!:) 但说真的,我同意Ned的观点,你正在“注入”类所需的“依赖项”。这意味着你正在编写抽象而不是具体实现,这是首要的良好设计。 - Robben_Ford_Fan_boy
也许我完全错了,但在你的第二个例子中,依赖反转在哪里?调用代码完全处理所有依赖项以及生命周期管理...这怎么可能是 DI 呢? - code4life
@code4life:这是我的观点,也是我的问题。依赖注入是否可以在没有容器的情况下作为一种模式使用?在第二个代码块中,您会注意到我通过构造函数传递了所有依赖项。 - andy
刚刚收到了一个踩和逃跑!有什么原因吗? - andy
8个回答

15

根据马丁·福勒(Martin Fowler)的说法,他是这个领域的专家之一,依赖注入是另一个名字,用于实现抽象概念反转控制(完整文章)。

本质上,所有的依赖注入意味着:类所依赖的对象被外部初始化并传递给它,而不是类本身负责获取/初始化这些对象。如何实现这一点超出了模式的范围。

反转控制也是同样的道理,但正如马丁·福勒所说,“反转控制”这个说法描述起来非常晦涩难懂。

个人认为,“依赖注入”也没有好到哪里去。我会将其描述为“正确使用构造函数”。

非IoC/非DI示例:

class Foo
{
    void DoSomething()
    {
        DbConnection myConnection = new DbConnection(ConfigurationSettings...);
        myConnection.ExecuteCommand();
    }
}

使用IoC/DI的相同事情:

class Foo
{
    private DbConnection myConnection;

    public Foo(DbConnection conn)
    {
        myConnection = conn;
    }

    void DoSomething()
    {
        myConnection.ExecuteCommand();
    }
}

我对那些认为除非有显式的绑定器/组装器/等等将注入类型与具体实现连接起来,否则它不是真正的IoC/DI的观点持有不同意见。因为接收依赖项的类并不知道区别。无论您是在外部类中还是在外部配置文件中安排依赖项,都是实现细节。


在详细说明后加上+1。对于“正确使用构造函数”,给予虚拟+1。 - Adam Robinson
@andy 如果这两个对象提供了PlayerService操作所需的所有外部功能,那么是的,它就是IoC/DI。 - Rex M
谢谢Rex,但你还没有完全解决DI模式是否可以在没有容器的情况下存在的问题。我意识到我的第二个代码块不能自动解析东西,但是当然它没有容器就不能。你是说抽象模式需要一个容器吗?请参见下面Ned的答案:https://dev59.com/BXA75IYBdhLWcg3wuLjD#3145786 - andy
@Owen S.:你认为任何向构造函数传递值或设置属性的对象都是DI模式吗?对我来说,这听起来有点傻。我认为我们不能忽略将显式代码移除以满足依赖关系的重要性,这是真正的DI系统的关键要素。这似乎是“注入”试图传达的内容——依赖关系出现在依赖类内部,而无需了解表面信息。 - codekaizen
将另一个问题标记为答案,请参见该答案的评论,了解原因,尽管它们都是很好的答案。 - andy
显示剩余10条评论

4

那么,DI能否作为一种模式而不需要任何额外的框架来使用?

是的,绝对可以。

如果是这样,上面的代码是它的一个例子吗?

是的,绝对是。

最后,如果没有容器的概念,有什么定义DI模式呢?

将一个对象所依赖的所有内容都注入进去;它的依赖关系被注入到其中。


1
+1 简单明了,精简迅速,正如我们所喜欢的一样!=) - Will Marcouiller
1
同意,我不确定为什么第一个答案得到了比其他答案更多的赞?? - andy

3
在Java世界中,依赖注入通常涉及框架、容器等等。但其实并不需要那些机械操作。
依赖注入的本质在于一个类能够被分配它所依赖的对象而不是在实现中硬编码。如果你可以从类外部“注入”这些“依赖项”,那么你就有了依赖注入。使用框架可能是一种好方法,但并非必需。
在Python世界中,没有依赖注入的框架文化,因此许多程序员会想知道这到底是怎么回事。对他们来说,DI就是“仅仅”能够将对象或类传递到构造函数中。
回答你具体的问题:
1. 是的,可以不用框架实现DI。 2. 是的,上面的代码就是一个例子:PlayerService不知道PlayerClient或PlayerSkinClient来自哪里。它们被注入到类中。请注意,其他人在这里回答了“否”。 3. 见上方。

我同意Ned...似乎没有人提到过这个问题? - andy
将此标记为答案可能看起来有些“有争议”,但我认为Ned在这里基本上说了Rex所说的话,只是更简单,更专注于问题...尽管它们都是很好的答案。 - andy

1
  1. 是的,虽然与像PicoContainer这样的东西相比 - 或其他真正微小的容器,所有这些都经过测试和深思熟虑 - 最终你将实现相同的功能集。

    最好的例子是Ayende的"在15行中构建IoC容器"

  2. 修改我的答案,我要改成是。我认为这里的主要认知失调是由于开发人员混淆了许多/大多数DI/IoC框架提供了整洁的构造对象的方法,这些方法看起来不像new,这就使它们符合IoC的思想。

  3. 如上所述,Martin Fowler的定义是规范的,并且已经在其他地方得到了很好的涵盖。


谢谢Marc。但是针对我的第二个问题,如果没有容器,你会怎么做?那真的是我的问题。 - andy
Glurp。Ned的观点实际上非常好。我关注的是您一次性组合对象的构成,而不是您将客户端和皮肤客户端的构建推迟到服务构建之外的事实。撤回该部分。 - Marc Bollinger

1

有趣的是,第一个例子根本不是 DI,而是 服务定位器反模式 的一个例子 - 没有任何东西被 注入

你自己的例子是纯 DI - 更具体地说是 构造函数注入 模式的实现。

DI 不是一个模式,而是一组模式和原则。当我们手动连接所有依赖项时,通常称之为 穷人版 DI。换句话说,DI 容器是可选的,但强烈推荐使用 :)

另请参阅 这篇关于 DI 容器带来的好处的描述



0

这真的不是依赖注入的示例。

原因是您正在安排依赖项(在您的情况下是 PlayerClientPlayerSkinClient),而不是让容器在构建具体的 IPlayerService 时为您安排依赖项。这就是“控制反转”——您调用了构造函数,而不是被调用。这是使用 DI 模式的主要原因,并且拥有一个范围、解析并将依赖关系放入请求它们的对象的容器是具体实现此目标的方法。

当您让容器管理依赖项传递时,您无需编写和维护将依赖项传递给具体的 IPlayerService 的代码。这使您可以创建其他的 PlayerClientPlayerSkinClient 实现(例如,在构建测试时模拟它们),而无需更新初始化 IPlayerService 的代码。这就是 DI 模式的有用之处。


同意,但这假定存在一个容器。我明白这一点,但我的问题是,依赖注入是否可以在没有容器的情况下存在,以及如何实现? - andy
@andy:我的观点是,其实并不是这样。如果你考虑这种关系,需要有一些东西来连接这个具体的类。那么是什么来完成这个任务呢?需要一个能够解析所依赖的类型并将它们传递给相关类型的东西。这就是一个容器,无论它采取什么形式。 - codekaizen

0

以下是一些与该主题相关的其他文章,按照“应该首先阅读”的顺序列出:

  1. 解密依赖注入;
  2. 依赖注入

阅读完这些文章后,我对该主题有了更好的理解。


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