4

我一直在阅读依赖注入,并且我理解在 XML 中指定依赖项的吸引力,就像在许多框架中所做的那样。我在一个大型系统上工作,我们通常调用工厂来获取具体对象,并且正在努力理解为什么如这篇 Wikipedia 文章中所示的手动注入依赖项据说会更好。

在我看来,调用工厂更好,因为:

  1. 调用代码不需要知道或关心是否存在特定的依赖项。
  2. 如果向被调用者添加了新的依赖项,则调用代码不需要更改。
  3. 调用代码不需要任何专门用于选择要注入的具体实例的逻辑。

在我看来,依赖注入仅在调用代码必须决定依赖的具体类时才提供好处。几乎就像“这是我的数据,现在处理它”。

有什么我错过的吗?

更新: 为了澄清,我们现有的代码大多直接调用工厂。所以要获得一个新的 Ball 对象,你会看到如下代码:

Ball myBall = BallFactory.getObject();

许多这些工厂被实现为允许新的具体对象类型的运行时注册 - 一个插件框架。

因此,在查看了一些初始注释之后,似乎使用 DI 我的调用代码通常不会传入 Ball 对象,而是传入 BallFactory。我想这样做的好处是该类可能更通用,因为它甚至没有与它使用的工厂耦合。

4

4 回答 4

3

依赖注入有助于单元测试。它允许您分离和隔离类的功能,因为它的任何依赖项都可以注入(因此也可以模拟)到类中。如果依赖项访问外部资源(例如 DB),这将特别有用。

我最近读了一篇文章,展示了依赖注入在测试中的优势。它专门针对静态方法,但同样适用于工厂。

http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/

就像@deceze 说的那样,如果你注入你的工厂,你就会两全其美......

于 2012-08-17T14:02:29.477 回答
2

两者都不比另一个“更好”;你可以单独使用依赖注入,也可以单独使用工厂,或者结合使用这些。

此外,您提到的关于工厂的所有 3 点对于依赖注入同样有效。请记住,可以在任何时间点注入依赖项,而不必立即通过“调用代码”进行注入。事实上,这就是 DI 框架为您所做的——它基本上是一个巨大的工厂,可以创建您的主应用程序对象并为您注入所有依赖项。

仅当您的代码需要能够在运行时创建依赖项的新实例时,显式使用工厂才有用。否则,在应用程序启动期间简单地使用 DI 框架注入所有静态依赖项要简单得多。

于 2012-08-18T04:48:52.053 回答
2

结合使用依赖注入和抽象工厂通常很有用——但有两个不同的原因。使用(手动)依赖注入的原因是它允许您在单元测试期间注入一个特殊的对象。如果您的设计描述调用代码不应负责创建对象(如您的 1-2-3 项目符号),则提供的依赖项应是抽象工厂的实例。注入的对象将在需要时使用工厂创建对象。

假设您使用两个工厂来生成依赖项(这里只有一个依赖项,Dice),用于轻松和困难的双陆棋游戏:

public class EasyGameFactory implements GameFactory
{
  Dice createDice()
  {
    return new LuckyDice();
  }
}

public class NormalGameFactory implements GameFactory
{
  Dice createDice()
  {
    return new RandomDice();
  }
}

出于单元测试的目的,您真的不希望使用任何 Dice 实现,因此您编写了 GameFactory 的特殊实现:

public class CustomGameFactory implements GameFactory
{
  private Dice mDice;

  public CustomGameFactory(Dice dice)
  {
    mDice = dice;
  }

  Dice createDice()
  {
    return mDice;
  }
}

该工厂不必是您的生产代码树的一部分。您通过测试代码向工厂提供 Dice 的特殊实现:

public class TestBackgammon
{
  @Test public void shouldReturnDiceThrown() 
  {
    SettableDice dice = new SettableDice();
    Game game = new GameImpl(new CustomGameFactory(dice));

    dice.setDice(new int[] {4, 5});
    game.nextTurn();
    assertArrayEquals(new int[] {4, 5}, game.diceThrown());
  }
}
于 2012-08-18T08:27:14.060 回答
1

是否使用依赖注入有点像 C 语言中 usingprintf和 using的区别fprintf。呼叫者以必须做出选择为代价获得了灵活性。当调用者不想要灵活性时(例如,如果您的程序的所有输出都进入标准输出,而不是标准错误或文件),灵活性是纯粹的负担,因为调用者总是必须传递相同的“正确”值。

如果您将依赖注入视为纯粹的负担,那意味着您的调用者并没有真正使用它。

您的第 1 点和第 3 点都说,“调用代码影响所发生事情的自由度较小”,这并不总是一个优势。测试代码特别受益于注入依赖项,但也可能存在调用者出于其他原因需要灵活性的情况。您使用 登录printf,还是通过在注入的记录器上调用函数进行登录?

第 2 点归结为您如何发展 API。是的,如果原始设计需要更改,那么通过使用固定的隐藏依赖项,您可以保护 API 不反映该更改。有时您可以使用新依赖项的默认值维护旧 API,并在某处添加带有额外参数的新方法/构造函数。

尽管如此,我使用过的语言中的标准库并不需要大量的依赖注入。所以你并不是唯一一个认为你的 API 不需要它的人,但我怀疑你可以从内部获得比现在更多的东西。例如,您可以在不连接到远程机器的情况下测试您的网络代码吗?如果没有,请考虑您的测试程序的这一部分是否会更容易、更快,并在可能的情况下提供更准确的诊断。

于 2012-08-17T14:14:35.020 回答