C# 依赖注入和特定依赖实现
首先让我介绍无依赖注入的实现(这将打破依赖倒置原则): 要使此代码可测试,请插入IChecksumGenerator:C# 依赖注入和特定依赖实现,c#,.net,dependency-injection,C#,.net,Dependency Injection,首先让我介绍无依赖注入的实现(这将打破依赖倒置原则): 要使此代码可测试,请插入IChecksumGenerator: public class MyValidator { private readonly IChecksumGenerator _checksumGenerator; public MyValidator(IChecksumGenerator checksumGenerator) { _checksumGenerator = checksumGenerato
public class MyValidator
{
private readonly IChecksumGenerator _checksumGenerator;
public MyValidator(IChecksumGenerator checksumGenerator)
{
_checksumGenerator = checksumGenerator;
}
...
}
现在,如果需要,我们可以轻松地测试MyValidator和存根checksumGenerator。但MyValidator实现在算法上与特定的IChecksumGenerator实现耦合(它只是不能与任何其他实现一起工作)。因此出现了一些问题:
public class MyValidator
{
private readonly IChecksumGenerator _checksumGenerator;
public MyValidator()
{
_checksumGenerator = new MyChecksumGenerator;
}
internal MyValidator(IChecksumValidator checksumValidator)
{
_checksumValidator = checksumValidator;
}
...
}
在这里,我引入了用于测试目的的特殊构造函数(这样我可以在测试中存根IChecksumValidator),但公共构造函数创建了它所耦合的实现(这样封装就不会被破坏)。为测试目的创建一些代码有点难看,但在这种情况下似乎是有意义的
如何解决这个问题?您需要将测试代码与产品代码分开 在产品代码中,您可以使用:
var validator = new MyValidator(new MyChecksumGenerator());
在测试代码中:
var validator = new MyValidator(new MyChecksumGeneratorStub());
其中MyChecksumGeneratorStub实现IChecksumGenerator。这并不违反封装。验证通常涉及校验和 我不会担心配置错误的ioc容器,因为在交付生产的产品中不存在伪造或模拟的实现。烟雾测试将立即发现这一点
希望这有帮助 我看不出MyValidator是如何通过算法耦合到IChecksumGenerator的。IChecksumGenerator将向MyValidator提供一个合同,给定一组特定的输入,将返回一组特定的输出 IChecksumGenerator的实现如何计算这些输出与MyValidator无关。这就是为什么您能够提供一个测试存根。测试存根在输入和输出之间有一个硬编码映射,使您能够进行测试。真正的实现将使用一个算法 一个算法可以有许多不同的实现。可以有一个优化内存使用的实现,还有一个优化速度的实现 只要为每个可能的输入提供了正确的输出,MyValidator就不应该关心实现。确保这一点是测试的全部内容
HPower,如果它真的是算法耦合的,并且没有办法将两者分开,那么它们可能不应该是单独的类。重构到构造函数注入是一个非常好的主意,但我发现问题中提出的约束很奇怪。我建议你重新考虑一下这个设计
如果MyValidator只与IChecksumGenerator的一个特定实现一起工作,那么它将违反(LSP)。本质上,这也意味着您将无法注入,因为stub/mock/fake/whatever不是“正确的”IChecksumGenerator的实例 在某种意义上,你可以说API对它的需求撒谎,因为它声称它可以处理任何IChecksumGenerator,而实际上它只处理一种特定的类型——让我们称它为OneOnlyCheckSumGenerator。虽然我建议重新设计应用程序以遵守LSP,但您也可以更改构造函数签名以诚实地说明要求:
public class MyValidator
{
private readonly OneAndOnlyChecksumGenerator checksumGenerator;
public MyValidator(OneAndOnlyChecksumGenerator checksumGenerator)
{
this.checksumGenerator = checksumGenerator;
}
// ...
}
通过使战略成员虚拟化,您可能仍然能够将OneAndOnlyChecksumGenerator变成测试双元组,这样您就可以创建特定于测试的子类。在本书中,Mark Seemann将最后一个解决方案称为私生子注入,并将其视为反模式。这主要是因为您仍然依赖于具体的实现。你应该看看这本书了解更多细节
在这里指定的情况下,在我看来很可能会有其他代码,在其他地方,生成任何正在验证的代码。我称之为造物主。反过来,这个创建者也可能需要一个IChecksumGenerator。在这种情况下,我会让DI容器完全控制依赖关系
想象一下,您想要为IChecksumGenerator切换到另一个实现。假设我上面所说的是真的,你们需要在两个地方改变这一点和私生子注射;创建者和验证器。让DI容器控制它将意味着它只有一个位置——容器配置
让DI容器拥有控制权的另一个好处是,通过引入LSP冲突,它减少了将来的更改将MyValidator更紧密地耦合到具体的IChecksumValidator的可能性。如果MyValidator只与IChecksumGenerator的一个特定实现一起工作,那么您将如何用测试双精度替换它?在任何情况下,你都会违反LSP…@马克:关于LSP的观点很好。请把它表述为一个答案。但是我没有理解你关于双重测试的观点为什么这不是违反封装?它是公开依赖项,而不是依赖项的实现方式。关于LSP,你是绝对正确的-我只需要传递一个更具体的依赖项
public class MyValidator
{
private readonly OneAndOnlyChecksumGenerator checksumGenerator;
public MyValidator(OneAndOnlyChecksumGenerator checksumGenerator)
{
this.checksumGenerator = checksumGenerator;
}
// ...
}