依赖注入可选参数

时间:2012-04-20 20:31:29

标签: c# asp.net-mvc dependency-injection optional-parameters

在使用带有构造函数注入的依赖注入框架时,使用可选参数是不是一种错误的做法?

示例:

public class ProductsController
{
    public ProductsController(IProductService productService = null, IBackOrderService = null)
    {
    }
}

我已将这两个参数指定为可选参数,但我的DI框架将始终注入两个依赖项。如果我向我的控制器添加一个需要新依赖项的新操作,那么使新的依赖项可选是不是很糟糕?即使现有的测试不需要新的依赖,我也可能会打破几十个单元测试。

修改
人们似乎对我的问题感到困惑。我永远不会在我的Web应用程序中手动构建ProductsController。这由控制器工厂处理(它会自动注入依赖项)。

我不喜欢的是进行这样的单元测试:

[Test]
public void Test1()
{
    var controller = new ProductsController(new MockProductService(), new MockBackOrderService());
}

现在我决定向我的控制器添加一个新的动作方法。此新操作需要新的依赖项,但现有操作都不需要。现在我必须返回并修改100个不同的单元测试,因为我添加了一个新参数。我可以通过使参数可选来避免这种情况,但我想知道这是不是一个坏主意。我的直觉不是,因为它唯一影响的是单元测试。

3 个答案:

答案 0 :(得分:6)

我认为这不是一个好主意。构造函数注入意味着依赖项是必需的。如果其中一个参数为null,您甚至应该添加引发的保护线。

我认为问题在于您的单元测试。例如,我只有一个地方创建了控制器,并且模拟了支持对象(controllerContext,HttpContext,Request,Response等)。然后,如果我在构造函数中添加一个新参数,我必须在单元测试中的一个位置更改它。

也许您应该考虑在单元测试中编写通用基类,或者为测试使用“setup”例程。

答案 1 :(得分:0)

考虑使用The Test Data Builder pattern

在最简单的形式中,它可以作为静态测试辅助方法

完成
public ProductsController BuildControllerWIthMockDependencies ()
{
    var controller = new ProductsController(new MockProductService(), new MockBackOrderService());
return controller;
}

您可以使用AutoFixture as a generic Test Data Builder

可以在Test Data Builders: an alternative to the Object Mother pattern

中找到使用测试数据构建器的其他技术

答案 2 :(得分:0)

对于定义依赖性的所有情况,我完全同意接受的答案

但是如果,如果您有某些东西不一定需要依赖项,但是如果该依赖项已加载,那么您希望能够进行配置。好...?听起来有些怪异,但是它是有效的元编程用例-您认为工厂模式可能会有所帮助..但是即使工厂可能需要一些,不需要或全部依赖项,因此Factory无法解决此问题

  

现实世界中的特征标记问题。如果功能关闭   不需要那个依赖..或者甚至不能创建它,因为   没有具体的实现。所以它关闭了。它编译,   一切正常。但是后来有人打开了所有功能   突然之间,我们需要这种依赖性。

我找到了一种方法来做到这一点-最好的部分是,我仅通过学习另一种不太熟悉的依赖注入技术(我正在使用Microsoft.Extensions.DependencyInjection)来实现该方法

为单个接口注入几种具体的实现方式

services.AddTransient<IWarehouseRepo, ActionRepository>();
services.AddTransient<IWarehouseRepo, EventRepository>();
services.AddTransient<IWarehouseRepo, AuditRepository>();
services.AddTransient<IWarehouseRepo, ProRepository>();
services.AddTransient<IWarehouseRepo, SuperWarehouseRepository>();
services.AddTransient<IWarehouseRepo, InferiorWarehouseRepository>();
services.AddTransient<IWarehouseRepo, MonthlyStatisticsRepository>();

我最近才知道这是完全有效的,但是要使其正常工作,您的构造函数必须看起来像这样。

public WarehouseHandler(IEnumerable<IWarehouseRepo> repos)

那真是太酷了!我可以根据任何条件选择所需的存储库。但是这对可选依赖项有何帮助?

因为这种构造方法将为您提供0 或更多的依赖关系。因此如果您不添加任何依赖项,则可以使用条件语句

在构造函数中进行分支
  if (repos.Count() == 0)
    return;

这是null引用安全的方法,不需要默认值,易于调试,易于阅读且易于实现。

您现在也可以将此技术用作功能切换机制!