依赖注入的最佳定义是什么?


10

每次有人联系我,并要求我以概念性方式定义“依赖项注入”,并解释在软件设计中使用DI的真正利弊。我承认我很难解释DI的概念。每次我需要告诉他们有关单一责任原则,组成而不是继承的历史时。

任何人都可以帮助我解释为开发人员描述DI的最佳方法吗?


2
这里的挑战是DI的定义如此之多。我采取“纯DI”的立场:如果我有一个依赖于其参数来提供所有状态,数据等的函数,则该函数正在使用DI。在另一个极端,有人会争辩说,没有DI框架,就不会有依赖项注入(尽管它们当然是错误的;)。因此,除非您能确定定义,否则您将无法开始解释它的含义……
David Arno

因此,据我了解,这不仅是我的问题。
Tiago Sampaio



归结为:依赖注入是一种用于实现依赖倒置的技术。其他一切只是在此基础上构建的额外内容。请注意,在这两个术语中,“依赖性”一词的含义略有不同。在依赖注入中,它指代代码所依赖的组件。在依赖关系反转中,它指的是(有向的)关系本身,即我们要反转的关系。后者是目标,因此主要优点和缺点是相同的;加上一些与实际实现有关的额外问题,例如对象生存期管理。
FilipMilovanović19年

Answers:


22

依赖注入是一个非常简单的概念的可怕名称(IMO)1。这是一个例子:

  1. 您有一个执行X的方法(或带有方法的类)(例如,从数据库检索数据)
  2. 作为执行X的一部分,所述方法创建和管理内部资源(例如DbContext)。此内部资源称为依赖项
  3. DbContext从方法中删除了资源的创建和管理(即),并使调用者有责任提供此资源(作为方法参数或在实例化类时)
  4. 您现在正在执行依赖项注入。


[1]:我来自较低层次的背景,花了几个月的时间坐下来学习依赖注入,因为它的名称暗示它会复杂得多,例如DLL Injection。是的Visual Studio(一般和我们的开发人员)是指.NET库(DLL,或者事实上组件),一个项目取决于作为依赖于所有没有帮助。甚至还有诸如Dependency Walker(depends.exe)之类的东西。


[编辑]我认为一些演示代码对某些人来说很方便,所以这里是一个(在C#中)。

没有依赖项注入:

public class Repository : IDisposable
{
    protected DbContext Context { get; }

    public Repository()
    {
        Context = new DbContext("name=MyEntities");
    }

    public void Dispose()
    {
        Context.Dispose();
    }
}

然后,您的消费者将执行以下操作:

using ( var repository = new Repository() )
{
    // work
}

使用依赖项注入模式实现的同一类如下所示:

public class RepositoryWithDI
{
    protected DbContext Context { get; }

    public RepositoryWithDI(DbContext context)
    {
        Context = context;
    }
}

现在,调用者有责任实例化a DbContext并将其传递(errm,inject)到您的类中:

using ( var context = new DbContext("name=MyEntities") )
{
    var repository = new RepositoryWithDI(context);

    // work
}

3
这应该添加到Wikipedia。
Evorlor '19

2
现在,调用者负责实例化DbContext-我认为这将是应用程序入口点实例化所有必需依赖项的责任。因此,消费者只需要在自己的合同中引入所需的类型作为依赖项即可。
法比奥

@Fabio可能是。(在这种情况下,调用者的责任是将在应用程序启动时实例化的资源提供给被调用的方法/类。)但是,在我的示例中,这不是必需的,因为这不是解释依赖性注入概念的要求。 。
Marc.2377 '19

5

通常可以使用现实世界的类比更好地解释抽象概念。这是我的比喻:

您经营一家三明治店。您做出了惊人的三明治,但对面包本身一无所知。你只有白面包。您的工作完全集中在您用来将面包变成三明治的配料上。

但是,您的某些客户确实会更喜欢黑面包。有些人更喜欢全麦。您其实都不在乎这两种方式,只要它是类似大小的面包,就可以制作出任何令人赞叹的三明治。您还真的不想承担购买几种面包和增加库存的额外责任。即使您库存了几种类型的面包,也总会有一些您无法合理预见的带有某种异国风味面包的顾客。

因此,您制定了一条新规则:客户自备面包。您不再自己提供任何面包。这是一个双赢的局面:客户可以得到他们想要的确切的面包,而您不再需要费心购买不需要的面包。毕竟,您是三明治制造商,而不是面包师。

哦,为了容纳那些不想购买自己面包的顾客,您可以在隔壁开设第二家商店,出售原始的无味白面包。不自带面包的客户只需获得默认面包,然后找您来做三明治。

它并不完美,但是它突出了关键功能:将控制权交给消费者。内在的双赢是,您不再需要获取自己的依赖关系,并且消费者在选择依赖关系时不受阻碍。


1
我喜欢这个,但OP正在为开发人员寻求解释。最初的抽象是好的,但是迟早需要一个真实的例子。
罗比迪

1
@RobbieDee:当该模式的目的明确时,其实现也趋于清晰。例如,马克的答案是绝对正确的,但我觉得他所使用的示例情境的复杂性使解释陷入困境。这可以归结为“如果您想建造一艘船,请不要鼓动人们来收集木材,不要给他们分配任务和工作,而要教他们渴望无尽的海洋。” 。我宁愿解释为什么也不愿解释该怎么做。
平坦

2
您当然是对的,但是我忍不住想我需要一个切实的例子-例如没有真实的文件系统或数据库来激发我的胃口,但也许那只是我狭窄的开发人员观点:)
Robbie Dee19年

1

简单的答案是:

首先,一个班级应该有明确的责任,这个范围之外的一切都应该放在该班级之外。如此说来,依赖注入就是当您使用“第三方”的帮助将其他B类的功能注入到A类中以实现关注点分离时,帮助A类完成超出其范围的某些操作。

.Net Core是一个很好的示例,因为该框架使用了大量依赖注入。通常,要注入的服务位于startup.cs文件中。

当然,学生应该了解一些概念,例如多态性,接口和OOP设计原则。


0

本质上讲,这是一个简单的概念,周围有很多绒毛和铺面。

当您可以很简单地在代码中完成操作时,也很容易陷入“ 我应该使用什么框架 ”的困扰。

这是我个人使用的定义:

给定行为X具有Y的依赖关系。依赖注入涉及提供满足Y实例条件的任何Y的功能,即使您没有Y。

一些示例可能是其中Y是文件系统或数据库连接。

诸如moq之类的框架允许使用接口定义双精度(Y的假装版本),因此可以插入Y的实例,其中Y例如是数据库连接。

容易陷入相信这纯粹是单元测试问题的陷阱,但是对于任何可能发生更改的代码来说,这都是一个非常有用的模式,并且无论如何都是很好的实践。


0

我们通过在运行时通过参数将行为插入函数的方法来提供函数的行为。

策略模式是依赖注入的一个很好的例子。


0

为此,我们必须首先定义依赖关系和注入。

  • 依赖性:操作需要的任何资源。
  • 注入:将资源传递给操作,通常作为方法的参数。

一个简单的例子就是将两个值相加的方法。显然,此方法需要将值相加。如果通过将它们作为参数传递来提供它们,则这已经是依赖注入的情况。另一种选择是将操作数实现为属性或全局变量。这样,就不会注入任何依赖关系,这些依赖关系可以在外部预先获得。

假设您改为使用属性,并分别命名为A和B。如果将名称更改为Op1和Op2,则会破坏Add方法。否则您的IDE会为您更新所有名称,关键是该方法也需要更新,因为它对外部资源具有依赖性。

此示例是基本示例,但您可以想象更复杂的示例,其中该方法对像图像这样的对象执行操作或从文件流中读取该对象。您是否想让方法到达图像,要求它知道它在哪里?否。您是否希望该方法打开文件本身,要求它知道在哪里寻找文件,甚至是从文件中读取文件?没有。

重点是:将一种方法的功能减少到其核心行为,并使该方法与环境分离。通过执行第二个操作可以得到第一个,您可以将其定义为依赖项注入。

好处:由于消除了对方法环境的依赖,因此对方法的更改不会影响环境,反之亦然。=>该应用程序变得易于维护(修改)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.