依赖注入的可行替代方案?

Dou*_*oug 35 dependency-injection

我不喜欢基于构造函数的依赖注入.

我认为这会增加代码复杂性并降低可维护性,我想知道是否有任何可行的替代方案.

我不是在讨论将实现与接口分离的概念,而是有一种从接口动态解析(递归)一组对象的方法.我完全支持这一点.但是,传统的基于构造函数的方法似乎有一些问题.

1)所有测试都取决于构造函数.

在去年的MVC 3 C#项目中广泛使用DI后,我发现我们的代码中包含以下内容:

public interface IMyClass {
   ...
}

public class MyClass : IMyClass { 

  public MyClass(ILogService log, IDataService data, IBlahService blah) {
    _log = log;
    _blah = blah;
    _data = data;
  }

  ...
}
Run Code Online (Sandbox Code Playgroud)

问题:如果我在实现中需要另一个服务,我必须修改构造函数; 这意味着该类的所有单元测试都会中断.

即使与新功能无关的测试也需要至少重构以添加其他参数并为该参数注入模拟.

这似乎是一个小问题,像resharper这样的自动化工具有所帮助,但是当这样的简单改变导致100多个测试中断时,它肯定很烦人.实际上我已经看到人们在做愚蠢的事情以避免更改构造函数而不是咬紧牙关并在发生这种情况时修复所有测试.

2)服务实例不必要地传递,增加了代码复杂性.

public class MyWorker {
  public MyWorker(int x, IMyClass service, ILogService logs) {
    ...    
  }
}
Run Code Online (Sandbox Code Playgroud)

如果我可以在给定服务可用并且已经自动解析的上下文(例如控制器)内,或者不幸的是,通过将服务实例传递给多个辅助类链来创建此类的实例.

我看到这样的代码,所有的时间:

public class BlahHelper {

  // Keep so we can create objects later
  var _service = null;

  public BlahHelper(IMyClass service) {
    _service = service;
  }

  public void DoSomething() {
    var worker = new SpecialPurposeWorker("flag", 100, service);
    var status = worker.DoSomethingElse();
    ...

  }
}
Run Code Online (Sandbox Code Playgroud)

万一例子是不明确的,就是我说的是向下传递解决DI接口实例,通过多层次,从没有理由比他们需要注入一些底层等.

如果某个类不依赖于某个服务,那么它应该依赖于该服务.在我看来,这种观点认为存在一种"瞬态"依赖关系,即一个类不使用服务而是简单地将其传递出去,这是一种废话.

但是,我不知道更好的解决方案.

没有这些问题,有什么能提供DI的好处吗?

我已经考虑在构造函数中使用DI框架,因为这解决了一些问题:

public MyClass() {
  _log = Register.Get().Resolve<ILogService>();
  _blah = Register.Get().Resolve<IBlahService>();
  _data = Register.Get().Resolve<IDataService>();
}
Run Code Online (Sandbox Code Playgroud)

这样做有什么缺点吗?

这意味着单元测试必须具有类的"先验知识",以便在测试初始化​​期间将mocks绑定到正确的类型,但我看不到任何其他缺点.

NB.我的例子是在c#中,但我也遇到了其他语言中的相同问题,尤其是那些工具支持不太成熟的语言,这些都是令人头疼的问题.

Ale*_*kov 18

在我看来,所有问题的根本原因是没有正确的DI.使用构造函数DI的主要目的是清楚地说明某些类的所有依赖关系.如果某些东西取决于某些东西,你总是有两种选择:使这种依赖性显式化或将其隐藏在某种机制中(这种方式往往带来比利润更多的麻烦).

让我们来看看你的陈述:

所有测试都取决于构造函数.

[剪断]

问题:如果我在实现中需要另一个服务,我必须修改构造函数; 这意味着该类的所有单元测试都会中断.

使课程取决于其他一些服务是一个相当重大的变化.如果您有多个服务实现相同的功能,我会认为存在设计问题.正确的模拟和测试满足SRP(在单元测试方面归结为"为每个测试用例编写单独的测试")并且独立应该解决这个问题.

2)服务实例不必要地传递,增加了代码复杂性.

DI的最常见用途之一是将对象创建与业务逻辑分开.在你的情况下,我们看到你真正需要的是创建一些Worker,而这又需要通过整个对象图注入的几个依赖项.解决此问题的最佳方法是永远不要new在业务逻辑中执行任何操作.对于这种情况,我宁愿注入一个工人工厂,从工人的实际创建中抽象出业务代码.

我已经考虑在构造函数中使用DI框架,因为这解决了一些问题:

public MyClass() {
  _log = Register.Get().Resolve<ILogService>();
  _blah = Register.Get().Resolve<IBlahService>();
  _data = Register.Get().Resolve<IDataService>();
}
Run Code Online (Sandbox Code Playgroud)

这样做有什么缺点吗?

作为一个好处,您将获得使用Singleton模式的所有缺点(不可测试的代码和应用程​​序的巨大状态空间).

所以我会说DI应该做得对(就像任何其他工具一样).您的问题(IMO)的解决方案在于理解您的团队成员的DI和教育.

  • `Register.Get().Resolve <>()`是[ServiceLocator反模式]的完美示例(http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx).如果向服务添加一个依赖项会导致破坏许多测试用例,那么您应该重构测试.正如@Alex建议让他们关注SRP是一回事.另一种方法是使用[TestInitializeAttribute](http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.testinitializeattribute.aspx)(如果你使用的是MsTest)来放置在一个地方进行测试的主要设置. (4认同)
  • 有帮助。“受过更多教育”。这是如何被接受的答案? (2认同)

Mar*_*ann 15

对于这些问题,构造函数注入很容易出错,但它们实际上是不正确实现的症状,而不是构造函数注入的缺点.

让我们分别看一下每个明显的问题.

所有测试都取决于构造函数.

这里的问题实际上是单元测试与构造函数紧密耦合.这通常可以通过简单的SUT工厂来解决- 这个概念可以扩展到自动模拟容器中.

在任何情况下使用构造函数注入时,构造函数应该很简单,因此没有理由直接测试它们.它们是实现细节,是您编写的行为测试的副作用.

服务实例不必要地传递,增加了代码复杂性.

同意,这肯定是代码味道,但同样,气味在实施中.构造函数注入不能归咎于此.

发生这种情况时,就会出现Facade Service缺失的症状.而不是这样做:

public class BlahHelper {

    // Keep so we can create objects later
    var _service = null;

    public BlahHelper(IMyClass service) {
        _service = service;
    }

    public void DoSomething() {
        var worker = new SpecialPurposeWorker("flag", 100, service);
        var status = worker.DoSomethingElse();
        // ...

    }
}
Run Code Online (Sandbox Code Playgroud)

做这个:

public class BlahHelper {
    private readonly ISpecialPurposeWorkerFactory factory;

    public BlahHelper(ISpecialPurposeWorkerFactory factory) {
        this.factory = factory;
    }

    public void DoSomething() {
        var worker = this.factory.Create("flag", 100);
        var status = worker.DoSomethingElse();
        // ...

    }
}
Run Code Online (Sandbox Code Playgroud)

关于提出的解决方案

提出的解决方案是服务定位器,它只有缺点,没有任何好处.

  • 日志服务怎么样?这是一个问题,它被注入到每个班级...... (2认同)
  • 记录不是一项服务 - 这是一个跨领域的问题:http://stackoverflow.com/questions/7905110/logging-aspect-oriented-programming-and-dependency-injection-trying-to-make/7906547#7906547 (2认同)