依赖注入和特定的依赖实现

Sib*_*Guy 3 .net c# dependency-injection

首先让我介绍没有依赖注入的实现(这将打破依赖反转原则):

public class MyValidator
{
  private readonly IChecksumGenerator _checksumGenerator;

  public MyValidator()
  {
     _checksumGenerator = new MyChecksumGenerator();
  }

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

为了使此代码可测试,请注入IChecksumGenerator:

public class MyValidator
{
  private readonly IChecksumGenerator _checksumGenerator;

  public MyValidator(IChecksumGenerator checksumGenerator)
  {
    _checksumGenerator = checksumGenerator; 
  }

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

现在,如果需要,我们可以轻松地测试MyValidator和stub checksumGenerator。但是MyValidator实现在算法上与特定的IChecksumGenerator实现耦合(它不能与任何其他实现一起使用)。因此出现一些问题:

  1. 我们引入了可能会注入错误的IChecksumGenerator的可能性(例如,由于IoC容器配置错误)
  2. 由于MyValidator的私有实现细节(耦合到MyChecksumGenerator)超出了类,因此我们破坏了封装

我想到的最好的解决方案是:

public class MyValidator
{
  private readonly IChecksumGenerator _checksumGenerator;

  public MyValidator()
  {
    _checksumGenerator = new MyChecksumGenerator;
  }

  internal MyValidator(IChecksumValidator checksumValidator)
  {
    _checksumValidator = checksumValidator;
  }

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

在这里,我介绍了用于测试目的的特殊构造函数(因此我可以在测试中对IChecksumValidator进行存根),但是公共构造函数将创建与之耦合的实现(因此封装不会中断)。创建一些用于测试目的的代码有点难看,但是看起来在这种情况下是有意义的。

您将如何解决这个问题?

Mar*_*ann 5

重构为构造函数注入是一个很好的主意,但是我发现问题中提出的约束很奇怪。我建议您重新考虑设计。

如果MyValidator仅与IChecksumGenerator的一种特定实现一起使用,则将违反Liskov替代原则(LSP)。从本质上讲,这也意味着您将无法插入测试Double,因为存根/模拟/伪造/无论什么都不是“正确的” IChecksumGenerator的实例。

从某种意义上讲,您可以说该API 取决于其要求,因为它声称它可以处理任何IChecksumGenerator,而实际上它仅适用于一种特定类型-我们将其称为OneAndOnlyChecksumGenerator。虽然我建议重新设计应用程序以遵守LSP,但是您也可以更改构造函数签名以诚实地满足要求:

public class MyValidator
{
    private readonly OneAndOnlyChecksumGenerator checksumGenerator;

    public MyValidator(OneAndOnlyChecksumGenerator checksumGenerator)
    {
        this.checksumGenerator = checksumGenerator; 
    }

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

通过使战略成员成为虚拟人员,您仍然可以将OneAndOnlyChecksumGenerator转换为Test Double,从而可以创建特定于测试的子类。