【问题标题】:Which dependencies should I inject?我应该注入哪些依赖项?
【发布时间】:2011-05-07 10:11:43
【问题描述】:

当使用依赖注入时,你会注入哪些依赖?

我之前已经注入了所有的依赖,但是在做TDD的时候发现通常有两种依赖:

  • 真正的外部依赖项可能会改变,例如产品存储库
  • 那些纯粹是为了可测试性而存在的,例如为可测试性而提取和注入的类的部分行为

一种方法是像这样注入所有依赖项

public ClassWithExternalDependency(IExternalDependency external,
    IExtractedForTestabilityDependency internal)
{
    // assign dependencies ...
}

但我发现这会导致 DI 注册表中的依赖项膨胀。

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

public ClassWithExternalDependency(IExternalDependency external)
    : this (external, new ConcreteClassOfInternalDependency())
{}

internal ClassWithExternalDependency(IExternalDependency external,
    IExtractedForTestabilityDependency internal)
{
    // assign dependencies ...
}

这是更多的努力,但似乎更有意义。缺点是并非所有对象都在 DI 框架中配置,从而打破了我听说过的“最佳实践”。

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

【问题讨论】:

    标签: dependency-injection


    【解决方案1】:

    我相信你最好注入所有依赖项。如果它开始变得有点笨拙,这可能表明您需要稍微简化一些事情或将依赖关系移动到另一个对象中。感受你设计的“痛苦”真的很有启发性。

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

    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();
                        });
        }
    

    即使我有几十个结果和演示者到处跑,我也不必明确地注册它们。

    【讨论】:

      【解决方案2】:

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

      尽可能多地反转依赖项,即使是那些你不需要测试的依赖项,这会使单元测试变得更加困难,并且你越多地存根,你真正测试系统的实际行为就越少。这使您的测试不太可靠。它还会使您在应用程序根目录中的 DI 配置复杂化。

      【讨论】:

      • 反转依赖关系的好处之一是能够单独测试单元。我可以提取一个计算器类来单独测试它,但也许它不需要注入(参见第二个代码示例)。我无法在现场对其进行测试,因为它会使计算器类客户端的测试变得臃肿。
      • 注入所有注射剂,新的所有新药。如果没有这种区别,DI 将要求所有对象在程序的整个持续时间内都存在。 Newables,又名值类型,代表惰性数据和可选的一些相关的转换行为(即接收值并返回新值的方法)。 Injectables,又名服务/业务类型,代表功能和可选的一些相关的程序状态。可以说,我们也有 I/O 类型来表示外部状态,例如文件()。 I/O 类型必须是可更新的,但应通过抽象工厂创建,以便模拟它们以进行测试。
      【解决方案3】:

      我会手动连接我所有的非外部依赖项,并且只“注册”外部依赖项。当我说非外部时,我的意思是属于我的组件的对象,这些对象只是为了单一的责任/可测试性而被提取到接口中,我永远不会有任何其他此类接口的实现。外部依赖项是不属于我的组件的数据库连接、Web 服务、接口之类的东西。我会将它们注册为接口,因为它们的实现可以切换到存根用于集成测试。在 DI 容器中注册少量组件使 DI 代码更易于阅读且不会膨胀。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-12
        • 1970-01-01
        • 2010-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-10
        相关资源
        最近更新 更多