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

Dependency injection 依赖注入的可行替代方案?,dependency-injection,Dependency Injection,我不喜欢基于构造函数的依赖注入 我相信这会增加代码的复杂性,降低可维护性,我想知道是否有可行的替代方案 我不是在谈论将实现与接口分离的概念,也不是在讨论从接口动态解析(递归)一组对象的方法。我完全支持这一点。然而,传统的基于构造函数的方法似乎存在一些问题 1) 所有测试都依赖于构造函数 去年在MVC 3 C项目中广泛使用DI后,我发现我们的代码中充满了这样的内容: public interface IMyClass { ... } public class MyClass : IMyCl

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

我相信这会增加代码的复杂性,降低可维护性,我想知道是否有可行的替代方案

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

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;
  }

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

即使是和新功能无关的测试,也至少需要重构以添加额外的参数,并为该参数注入模拟

这似乎是一个小问题,而且像resharper这样的自动化工具会提供帮助,但当这样一个简单的更改导致100多个测试失败时,这肯定会让人恼火。实际上,我见过人们为了避免改变构造函数而做一些愚蠢的事情,而不是在这种情况下咬紧牙关修复所有的测试

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

public class MyWorker {
  public MyWorker(int x, IMyClass service, ILogService logs) {
    ...    
  }
}
只有在给定服务可用且已自动解析的上下文(例如控制器)中,或者不幸的是,通过将服务实例向下传递到多个帮助器类链中,才能创建此类的实例

我一直看到这样的代码:

如果这个例子不清楚,我要说的是将解析的DI接口实例向下传递到多个层,除了在底层,它们需要注入到某些东西中

如果一个类不依赖于某个服务,那么它应该依赖于该服务。在我看来,有一种“暂时”依赖性的想法,即类不使用服务而只是将其传递给其他类,这是毫无意义的

然而,我不知道有更好的解决办法

有没有什么东西可以提供DI的好处而不存在这些问题

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

public MyClass() {
  _log = Register.Get().Resolve<ILogService>();
  _blah = Register.Get().Resolve<IBlahService>();
  _data = Register.Get().Resolve<IDataService>();
}
publicmyclass(){
_log=Register.Get().Resolve();
_blah=Register.Get().Resolve();
_data=Register.Get().Resolve();
}
这样做有什么坏处吗

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


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

在我看来,所有问题的根本原因是做得不对。使用构造函数DI的主要目的是清楚地说明某个类的所有依赖关系。如果某件事依赖于某件事,你总是有两种选择:明确这种依赖关系或将其隐藏在某种机制中(这种方式往往带来更多的麻烦而不是利润)

让我们回顾一下你的陈述:

所有测试都依赖于构造函数

[剪报]

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

使一个类依赖于其他服务是一个相当大的变化。如果您有多个服务实现相同的功能,我认为这是一个设计问题。正确的模拟和使测试满足SRP(就单元测试而言,SRP归结为“为每个测试用例编写一个单独的测试”),独立测试应该可以解决这个问题

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

public class MyWorker {
  public MyWorker(int x, IMyClass service, ILogService logs) {
    ...    
  }
}
DI最普遍的用途之一是将对象创建与业务逻辑分离。在您的例子中,我们看到您真正需要的是创建一些Worker,而Worker又需要通过整个对象图注入几个依赖项。解决此问题的最佳方法是永远不要在业务逻辑中执行任何
new
s。对于这种情况,我更愿意注入一个工人工厂,从工人的实际创建中抽象出业务代码

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

public MyClass() {
  _log = Register.Get().Resolve<ILogService>();
  _blah = Register.Get().Resolve<IBlahService>();
  _data = Register.Get().Resolve<IDataService>();
}
publicmyclass(){
_log=Register.Get().Resolve();
_blah=Register.Get().Resolve();
_data=Register.Get().Resolve();
}
这样做有什么坏处吗

作为一个好处,您将得到使用
Singleton
模式(不稳定的代码和巨大的应用程序状态空间)的所有缺点


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

很容易将这些问题归咎于构造函数注入,但它们实际上是实现不当的症状,而不是构造函数注入的缺点

让我们分别来看每个明显的问题

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

这里的问题实际上是单元测试与构造函数紧密耦合。这通常可以用一个简单的概念来弥补——这个概念可以扩展为一个简单的概念

在任何情况下,当使用构造函数注入时,都没有理由直接测试它们。它们是作为您编写的行为测试的副作用出现的实现细节

不必要地传递服务实例,增加了代码复杂性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(); // ... } }
public class Main {

    public static void main(String[] args) {
        // Making main Object-oriented
        Main mainRunner = new Main(null);
        mainRunner.mainRunner(args);
    }

    private final Context context;

    // This is an OO alternative approach to Java's main class.
    protected Main(String config) {
        context = new Context();

        // Set all context here.
        if (config != null || "".equals(config)) {
            Gson gson = new Gson();
            SomeServiceInterface service = gson.fromJson(config, SomeService.class);
            context.someService = service;
        }
    }

    public void mainRunner(String[] args) {
        ServiceManager manager = new ServiceManager(context);

        /**
         * This service be a mock/fake service, could be a real service. Depends on how
         * the context was setup.
         */
        SomeServiceInterface service = manager.getSomeService();
    }
}
public class MainTest {

    @Test
    public void testMainRunner() {
        System.out.println("mainRunner");
        String[] args = null;

        Main instance = new Main("{... json object for mocking ...}");
        instance.mainRunner(args);
    }

}