Dependency injection 我应该注入哪些依赖项?

Dependency injection 我应该注入哪些依赖项?,dependency-injection,Dependency Injection,使用依赖项注入时,您会注入哪些依赖项 我之前已经注入了所有依赖项,但发现在执行TDD时,通常有两种类型的依赖项: 可能发生变化的真正外部依赖项,例如ProductRepository 纯粹为可测试性而存在的,例如,仅为可测试性而提取和注入的类行为的一部分 一种方法是像这样注入所有依赖项 public ClassWithExternalDependency(IExternalDependency external, IExtractedForTestabilityDependency

使用依赖项注入时,您会注入哪些依赖项

我之前已经注入了所有依赖项,但发现在执行TDD时,通常有两种类型的依赖项:

  • 可能发生变化的真正外部依赖项,例如ProductRepository
  • 纯粹为可测试性而存在的,例如,仅为可测试性而提取和注入的类行为的一部分
一种方法是像这样注入所有依赖项

public ClassWithExternalDependency(IExternalDependency external,
    IExtractedForTestabilityDependency internal)
{
    // assign dependencies ...
}
public ClassWithExternalDependency(IExternalDependency external)
    : this (external, new ConcreteClassOfInternalDependency())
{}

internal ClassWithExternalDependency(IExternalDependency external,
    IExtractedForTestabilityDependency internal)
{
    // assign dependencies ...
}
但我发现这会导致DI注册表中的依赖项膨胀

另一种方法是像这样隐藏“可测试性依赖”

public ClassWithExternalDependency(IExternalDependency external,
    IExtractedForTestabilityDependency internal)
{
    // assign dependencies ...
}
public ClassWithExternalDependency(IExternalDependency external)
    : this (external, new ConcreteClassOfInternalDependency())
{}

internal ClassWithExternalDependency(IExternalDependency external,
    IExtractedForTestabilityDependency internal)
{
    // assign dependencies ...
}
这是更多的努力,但似乎更有意义。缺点是并非所有对象都在DI框架中配置,因此打破了我所听到的“最佳实践”


您会提倡哪种方法?为什么?

我认为最好是注入所有依赖项。如果它开始变得有点笨拙,这可能表明您需要简化一些事情,或者将依赖项移到另一个对象中。在设计过程中感受“痛苦”是非常有启发性的

至于注册表中的依赖项膨胀,您可以考虑使用某种常规绑定技术,而不是手工注册每个依赖项。某些IoC容器内置了基于约定的类型扫描绑定。例如,下面是我在Caliburn WPF应用程序中使用的模块的一部分,该应用程序使用Ninject:

public class AppModule : NinjectModule
{
    public override void Load()
    {
        Bind<IShellPresenter>().To<ShellPresenter>().InSingletonScope();

        BindAllResults();
        BindAllPresenters();
    }

    /// <summary>
    /// Automatically bind all presenters that haven't already been manually bound
    /// </summary>
    public void BindAllPresenters()
    {
        Type[] types = Assembly.GetExecutingAssembly().GetTypes();

        IEnumerable<Type> presenterImplementors =
            from t in types
            where !t.IsInterface
            && t.Name.EndsWith("Presenter")
            select t;

            presenterImplementors.Run(
                implementationType =>
                    {
                        if (!Kernel.GetBindings(implementationType).Any())
                            Bind(implementationType).ToSelf();
                    });
    }
公共类AppModule:ninject模块
{
公共覆盖无效负载()
{
绑定().To().InSingletonScope();
BindAllResults();
BindAllPresenters();
}
/// 
///自动绑定所有尚未手动绑定的演示者
/// 
public void BindAllPresenters()
{
Type[]types=Assembly.getExecutionGassembly().GetTypes();
IEnumerable presenterImplementors=
从t型开始
哪里!t.I界面
&&t.Name.EndsWith(“演示者”)
选择t;
presenterImplementors.Run(
实现类型=>
{
如果(!Kernel.GetBindings(implementationType).Any())
Bind(implementationType).ToSelf();
});
}

即使我有几十个结果和演示者,我也不必显式注册它们。

我当然不会注入所有依赖项,因为我们要停止吗?是否要注入
字符串
依赖项?我只反转单元测试所需的依赖项。我想存根我的数据库(例如,请参阅)。我要存根发送电子邮件。我要存根系统时钟。我要存根写入文件系统


尽可能多地反转依赖项,即使是测试不需要的依赖项,也会使单元测试变得更加困难,而且越是剔除依赖项,就越不可能真正测试系统的运行情况。这会降低测试的可靠性。这还会使应用程序根目录中的DI配置复杂化。

手动重新设置所有非外部依赖项,仅“注册”外部依赖项。当我说“非外部”时,我指的是属于我的组件的对象,它们被提取到接口中只是为了单个责任/可测试性,我永远不会有任何其他此类接口的实现。外部依赖项是不属于我的组件的DB连接、web服务和接口。我会将它们注册为接口,因为它们的实现可以切换到存根的实现进行集成测试。在DI容器中注册少量组件可以使DI代码更易于阅读和膨胀。

其中一个好处是反转依赖关系的目的是能够独立地测试单元。我可以提取一个计算器类来独立地测试它,但可能不需要注入(参见第二个代码示例)。我无法对其进行现场测试,因为这会增加计算器类客户端的测试。注入所有可注入项,新建所有可新建项。如果没有此区别,DI将要求所有对象在整个程序期间都处于活动状态。可新建项,也称为值类型,表示惰性数据和可选的一些相关转换行为(即,接受值并返回新值的方法)。可注入类型,也称为服务/业务类型,表示功能和一些可选的相关程序状态。可以说,我们也有i/O类型来表示外部状态,例如File().I/O类型必须是可更新的,但应通过抽象工厂创建,以便模拟测试。