【问题标题】:When to use Dependency Injection何时使用依赖注入
【发布时间】:2010-12-07 12:41:17
【问题描述】:

这几天我有一种感觉,依赖注入真的应该被称为“我无法下定决心”——模式。我知道这听起来可能很傻,但实际上它是关于为什么我应该使用依赖注入 (DI) 的原因。经常有人说我应该使用 DI 来实现更高级别的松散耦合,我得到了那个部分。但真的...一旦我的选择落在 MS SQL 或 MySQL 上,我多久更改一次数据库...很少对吗?

有没有人有一些非常令人信服的理由说明 DI 是要走的路?

【问题讨论】:

  • 到目前为止,我喜欢你的所有理由......我知道,“获得” DI 的主要是经验丰富的程序员。据我了解,DI 是关于质量的。作为程序员,我们希望能够生产出最高水平的软件——并且对其进行测试会有所帮助。这些年来我写了很多代码,我很高兴地说,在过去的 10 年里,超过 50% 的代码仍在运行......而且没有 DI。也许这可以帮助您理解为什么我对 DI 有点保留?
  • 显然DI相对来说是新的。所以当然你可以在没有它的情况下编写高质量的软件 - 但是随着测试框架和 TDD 等事物的进步,DI 可以使编写测试以及编写可在未来维护的代码变得更加容易(耦合)。

标签: dependency-injection


【解决方案1】:

两个词,单元测试

DI 最令人信服的原因之一是允许更轻松的单元测试,而无需访问数据库并担心设置“测试”数据。

【讨论】:

  • 所以你在谈论通过在单元测试期间使用一些假对象而不是更改我的数据库来解耦数据库并“伪造它”......嗯......听起来很合理论据!
  • 您的测试将运行得更快,而无需访问真正的数据库。另外,当您测试特定案例时,您可以更轻松地在模拟对象中设置这些特定案例,而不必担心您的测试数据库是否处于正确状态。
  • 如果您正在使用 TDD 进行开发(而且您应该这样做!),那么您每 30 秒运行一次测试套件。那么你不能承受超过几秒钟的运行时间。
  • 单元测试根本不需要 DI。如果您需要替换“真正的”数据库实现,那么设置一个模拟/假实现并测试...当然,这对于某些语言(例如 C++)或某些模拟工具并不总是可行的,但是它通常是可行的这一事实意味着单元测试本身并不总是一个有效的理由。
  • 对不起,我不认为这个答案是完整的。在本文中用“模拟”替换 DI 会更有意义。 DI 适用于更多场景,因为它鼓励组件之间的松散耦合。
【解决方案2】:

DI 对于解耦系统非常有用。如果您使用它的全部目的是将数据库实现与应用程序的其余部分分离,那么您的应用程序非常简单,或者您需要对问题域进行更多分析并发现问题域中的哪些组件是最有可能发生变化的组件以及系统中具有大量耦合的组件。

当您的目标是代码重用、多功能性和对问题域更改的稳健性时,DI 最有用。

它与您的项目的相关程度取决于您的代码的预期生命周期。根据您正在编写的大部分代码从一个项目到下一个项目进行零重用的工作类型,实际上可能是完全可以接受的。

使用 DI 的一个例子是创建一个应用程序,该应用程序可以为多个客户端部署,使用 DI 为客户端注入定制,也可以描述为 GOF 策略模式。使用 DI 框架可以促进许多 GOF 模式。

DI 与企业应用程序开发更相关,在这种应用程序开发中,您有大量代码、复杂的业务需求,并且期望(或希望)系统能够维持多年或数十年。

【讨论】:

  • 如果您寻求灵活性(因为您希望进行实验),它在快速开发过程中也很有用。您可以轻松连接不同的视图、数据结构、菜单、控制方案等等。它基本上(如果我理解并正确使用它)允许您以更灵活和更有条理的方式管理“依赖项”,同时减少样板代码噪音。当然,它可能会被滥用并以某种方式使代码变得更加复杂。
【解决方案3】:

即使您在开发阶段不更改程序的结构,您也会发现您需要从程序的不同部分访问多个子系统。使用 DI,您的每个班级只需要请求服务,您就不必手动提供所有接线。

这确实有助于我专注于软件设计中事物的交互,而不是“谁需要随身携带什么,因为其他人以后需要它”。

此外,它还节省了大量编写样板代码的工作。我需要单身吗?我只是将一个类配置为一个。我可以用这样的“单例”进行测试吗?是的,我仍然可以(因为我只是将它配置为只存在一次,但测试可以实例化一个替代实现)。

但是,顺便说一下,在我使用 DI 之前,我并没有真正了解它的价值,但尝试它确实让我大开眼界:我的设计比以前更加面向对象。 顺便说一句,对于当前的应用程序,我不进行单元测试(坏,坏我),但我仍然无法再与 DI 一起生活。移动东西和保持类的小而简单要容易得多。

【讨论】:

  • +1 提到了 DI/IoC 容器框架如何取代邪恶的单例模式!
  • 我看到的大多数 DI 框架至少有 2000 多行代码,这似乎是很多代码。此外,您似乎无法在不修改现有代码的情况下使用该框架。根据您的经验,将 DI 框架换成另一个框架容易吗?
  • @TrueWill 为什么单例是邪恶的,而 DI 版本是好的?
  • @zehelvion 大部分都归结为可测试性——DI 使得隔离和单元测试变得容易。 DI 还使依赖关系显式化;单例的使用可能很难看到。从实现的角度来看,DI 允许您从简单的类(单一职责)创建(有效的)单例。另见stackoverflow.com/questions/137975/…
  • 是的,我想总结一下:misko.hevery.com/2008/08/17/singletons-are-pathological-liars 你不希望复杂的构造/初始化散布在你的代码中,而是希望它有条理且易于更改。
【解决方案4】:

虽然我对 DB 示例半同意您的看法,但我发现对使用 DI 很有帮助的一件大事是帮助我测试我在数据库之上构建的层。

这是一个例子......

你有你的数据库。

您的代码可以访问数据库并返回对象

您有业务领域对象,它们采用前一项的对象并用它们做一些逻辑。

如果您将数据访问与业务领域逻辑合并,您的领域对象可能会变得难以测试。 DI 允许您将自己的数据访问对象注入到您的域中,这样您就不必依赖数据库进行测试或可能的演示(运行一个演示,其中一些数据是从 xml 而不是数据库中提取的)。

像这样抽象 3rd 方组件和框架也会对您有所帮助。

除了测试示例之外,还有一些地方可以通过按合同设计方法使用 DI。您可能会发现创建一个调用您要注入其中的对象的方法的各种处理引擎是合适的。虽然它可能不会真正“处理它”,但它会运行在您提供的每个对象中具有不同实现的方法。

我看到了一个例子,其中每个业务域对象都有一个“保存”函数,该函数在注入处理器后被调用。处理器用配置信息修改了组件,Save 处理了对象的主要状态。本质上,DI补充了符合接口的对象的多态方法实现。

【讨论】:

    【解决方案5】:

    依赖注入使您能够单独测试特定的代码单元。

    假设我有一个类 Foo,例如,它在其构造函数中采用类 Bar 的实例。 Foo 上的一种方法可能会检查 Bar 的属性值是否允许对 Bar 进行一些其他处理。

    public class Foo
    {
        private Bar _bar;
    
        public Foo(Bar bar)
        {
            _bar = bar;
        }
    
        public bool IsPropertyOfBarValid()
        {
            return _bar.SomeProperty == PropertyEnum.ValidProperty;
        }
    }
    

    现在假设Bar 被实例化,并且它的属性被设置为它的构造函数中来自某个数据源的数据。我该如何测试FooIsPropertyOfBarValid() 方法(忽略这是一个非常简单的例子)?好吧,Foo 依赖于传递给构造函数的Bar 的实例,而后者又依赖于其属性设置到的数据源中的数据。我们想要做的是有某种方法将Foo 从它所依赖的资源中隔离出来,以便我们可以单独测试它

    这就是依赖注入的用武之地。我们想要的是有某种方法来伪造Bar 的实例传递给Foo,这样我们就可以控制在这个伪造的Bar 上设置的属性并实现我们的目标开始做,测试IsPropertyOfBarValid() 的实现是否符合我们的预期,即在Bar.SomeProperty == PropertyEnum.ValidProperty 时返回true,对于任何其他值返回false。

    有两种类型的假对象,Mocks 和 Stubs。存根为被测应用程序提供输入,以便可以在其他东西上执行测试。另一方面,模拟为测试提供输入以决定通过\失败。

    Martin Fowler has a great article on the difference between Mocks and Stubs

    【讨论】:

      【解决方案6】:

      我认为当您有许多服务/组件必须在运行时根据外部配置选择其实现时,DI 值得使用。 (注意,这样的配置可以采用 XML 文件的形式,也可以是代码注释和单独类的组合;选择更方便的方式。)

      否则,我会简单地使用 ServiceLocator,它比整个 DI 框架“更轻”且更易于理解。

      对于单元测试,我更喜欢使用可以按需模拟对象的模拟 API,而不是要求将它们从测试中“注入”到被测单元中。对于 Java,我自己就有一个这样的库,JMockit

      【讨论】:

      • +1 用于提及服务定位器。我认为,当人们只使用它的“服务位置”方面时,有时 DI 似乎有点矫枉过正。
      【解决方案7】:

      除了松散耦合之外,由于 DI,任何类型的测试都可以更轻松地实现。您可以用模拟、虚拟甚至其他版本替换被测类的现有依赖项。如果创建一个类时直接实例化其依赖项,则在需要时通常很难甚至不可能“存根”它们。

      【讨论】:

      • 您能否详细说明“存根”和“模拟”这两个术语...我认为“模拟”是我所说的“假”对象
      • 通过存根我只是指用“其他”的东西替换任何依赖项进行测试。模拟我的意思是使用诸如 jmock 或 mockito 之类的库(如果您当然使用 java)来创建一个代理,您可以在其中声明对该“模拟”对象的期望。您还可以通过实现依赖项的接口来创建类的虚拟版本。
      • 当你模拟对象时,你是在di registery中注册它还是简单地将dummy object传入?例如通过构造函数。
      【解决方案8】:

      今晚我才明白。 对我来说,依赖注入是一种实例化对象的方法,它需要大量参数才能在特定上下文中工作。

      什么时候应该使用依赖注入? 如果您以静态方式实例化对象,则可以使用依赖注入。例如,如果您使用可以将对象转换为 XML 文件或 JSON 文件的类,并且您只需要 XML 文件。如果你不使用依赖注入,你将不得不实例化对象并配置很多东西。

      什么时候不应该使用 depandancy 注入? 如果使用请求参数实例化对象(在提交表单之后),则不应使用 depandancy 注入,因为该对象不是以静态方式实例化的。

      【讨论】:

      • 很多年前我就问过这个问题,从那时起我学到了很多东西。您所描述的内容可以通过使用 IoC 容器很好地解决,并使用 DI 注入创建的对象。因此,例如,您在 IoC 容器的注册表中配置您的“复杂”对象,并在您需要的代码中“请求”一个实例。我同意这也是使用 DI 的正当理由。
      猜你喜欢
      • 1970-01-01
      • 2013-09-17
      • 2018-09-03
      • 1970-01-01
      • 2013-01-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-21
      相关资源
      最近更新 更多