为什么依赖注入比使用工厂更好?

时间:2012-08-17 13:54:28

标签: oop language-agnostic dependency-injection

我一直在阅读依赖注入,我理解在XML中指定依赖项的吸引力,就像许多框架中所做的那样。我在一个大型系统上工作,我们通常会调用工厂来获取具体对象,并且很难理解为什么手动注入依赖关系,如图所示in this Wikipedia article应该更好。

在我看来,打电话给工厂更好,因为:

  1. 调用代码不需要知道或关心特定的依赖关系。
  2. 如果向被调用方添加新的依赖项,则无需更改调用代码。
  3. 调用代码不需要任何专用于选择要注入的具体实例的逻辑。
  4. 在我看来,当调用代码具有来决定依赖项的具体类时,依赖注入只会带来好处。几乎就像“这是我的数据,现在处理它。”

    我错过了什么吗?

    更新 为了澄清,我们现有的代码主要是直接调用工厂。因此,要获得一个新的Ball对象,您会看到如下代码:

    Ball myBall = BallFactory.getObject();
    

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

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

4 个答案:

答案 0 :(得分:3)

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

我最近读了一篇文章,证明了依赖注入在测试中的优势。它特别是关于静态方法,但同样适用于工厂。

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

就像@deceze所说,如果你注入你的工厂,你将获得两全其美......

答案 1 :(得分:2)

两者都没有“好”;你可以单独使用依赖注入,单独使用工厂,或者使用它们的组合。

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

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

答案 2 :(得分: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());
  }
}

答案 3 :(得分:1)

使用依赖注入与否有点像使用printf和使用fprintf之间的C差异。呼叫者以必须做出选择为代价获得灵活性。当调用者不想要灵活性时(例如,如果所有程序的输出都转到stdout,从不stderr或文件),灵活性是一个纯粹的负担,因为调用者总是必须传递相同的“正确”值。

如果你认为依赖注入是一种纯粹的负担,那就意味着你的调用者并没有真正使用它。

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

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

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