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