【问题标题】:Dependency Injection Optional Parameters依赖注入可选参数
【发布时间】:2012-04-20 20:31:29
【问题描述】:

在使用带有构造函数注入的依赖注入框架时使用可选参数是否被认为是一种不好的做法?

例子:

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 个不同的单元测试,因为我添加了一个新参数。我可以通过使参数可选来避免这种情况,但我想知道这是否是一个坏主意。我的直觉说不,因为它唯一影响的是单元测试。

【问题讨论】:

  • ProductsController 在没有依赖关系的情况下如何运作?
  • @Matthew ProductsController 的某些部分可以在没有任何依赖关系的情况下运行。不过,显然 DI 容器总是会注入所有依赖项。
  • 我从不手动实例化我的控制器,除非在我的单元测试中。这就是为什么我想知道是否将参数设为可选是否重要。默认情况下,参数将始终注入(在我的单元测试之外)。
  • 如果它们总是被注入到您的生产代码中,那么如果您不通过这些依赖项,您在测试什么?听起来不对。
  • 我正在使用模拟进行单元测试?我不使用依赖注入将模拟注入控制器。这似乎有点傻。

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


【解决方案1】:

我完全同意接受的答案,所有关于定义依赖项意味着没有它就无法实现的情况。

但是如果您有一些不一定需要依赖项但您希望能够在该依赖项已加载的情况下配置某些东西的情况。好的...?这听起来有点奇怪,但它是一个有效的元编程用例 - 你认为工厂模式可能会有所帮助.. 但即使是工厂 可能需要一些、没有或所有依赖项,所以工厂不能解决这个问题.

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

我找到了一种方法来做到这一点——最好的部分是我通过学习另一种不太知名的依赖注入技术(我正在使用 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;

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

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

【讨论】:

  • 赞成 - 这可能不是 OP 所要求的,但在不一定与单元测试相关的情况下肯定会有所帮助。
【解决方案2】:

我认为这不是一个好主意。构造函数注入意味着依赖是必需的。如果参数之一为空,您甚至应该添加引发的警戒线。

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

也许你应该考虑在你的单元测试中编写一个通用基类,或者为测试使用“设置”例程。

【讨论】:

  • 这就是我要找的。我可能确实需要将我的单元测试重构为只在一个地方进行设置。现在我一直在为此苦苦挣扎。感谢您的意见。
  • 我很想知道为什么这不是一个好主意。可选参数有什么问题,比如IMyDependency myDependency = null,然后让构造函数使用默认实现(如果不存在)-if (myDependency == null) myDependency = new NullMyDependency。在我看来,在某些情况下它很有用,而且它似乎适用于 Microsoft.Extensions.DependencyInjection
  • @Joe 通常构造函数注入意味着参数是必需的,我在可选参数成为事物之前应用了这个原则。您的对象已使用传入的参数进行初始化,您就完成了。对于可选的东西,通常使用属性注入。这是表达你的 API 的问题。当然,如果您为可选的构造函数参数提供有意义的默认值,它也会起作用。只是口味问题
【解决方案3】:

考虑使用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中找到

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-09
    • 2016-01-26
    • 1970-01-01
    • 2019-02-16
    • 2019-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多