依赖注入容器有什么意义?

时间:2017-06-30 13:53:22

标签: c# dependency-injection

使用.net核心,您可以注册"服务"据我所知,这意味着你可以将类型注册到具体的类。

因此,我决定了我学习DI并练习它的时间。我理解这个概念,通过测试它是非常有益的。然而令我困惑的是注册服务的想法以及它是否真正需要。

例如,如果我有:

public class MyClass
{
    public MyClass(IDataContext)
    {
         ... store it 
    }
}

然后这意味着我可以注入任何实现IDataContext的类,允许在测试中使用伪造和moq。但是为什么我会注册一个服务并将IDataContext映射到启动时的具体类?在其他方法中使用以下内容是否有问题:

DataContext dc = new DataContext(); // concrete
var c = new MyClass(dc);

修改

这个问题围绕着使用容器(服务)而不是为什么在构造函数中使用接口。

4 个答案:

答案 0 :(得分:2)

现在你放这个代码的那些类

public class MyService
{
    public void DoSomething()
    {
        DataContext dc = new DataContext(); // concrete
        var c = new MyClass(dc);
        c.DoSomething();
    }
}

严重依赖DataContextMyClass。所以你不能孤立地测试MyService。课程不应该关心其他班级如何做他们所做的事情,他们应该只关心他们按照他们所说的去做。这就是我们使用接口的原因。这是关注点的分离。一旦实现了这一点,就可以单独测试任何一段代码,而不依赖于外部代码的行为。

在一个位置预先注册您的依赖项也更清晰,这意味着您可以通过更改一个位置而不是搜索所有用法并单独更改它们来交换依赖项。

在我顶部的代码示例中,MyService要求同时使用DataContextMyClass。相反,它应该是这样的:

public class MyService
{
    private readonly IMyClass _myClass;

    public MyService(IMyClass myClass)
    {
        _myClass = myClass;
    }

    public void DoSomething()
    {
        _myClass.DoSomething();
    }
}

public interface IMyClass
{
    void DoSomething();
}

public class MyClass : IMyClass
{
    private readonly IDataContext _context;

    public MyClass(IDataContext context)
    {
        _context = context;
    }
    public void DoSomething()
    {
        _context.SaveSomeData();
    }
}

现在MyService根本不依赖DataContext,它不需要担心它,因为那不是它的工作。但它确实需要满足IMyClass的东西,但它并不关心它是如何实现的。 MyService.DoSomething()现在可以进行单元测试,而不依赖于其他代码的行为。

如果你没有使用容器来处理满足依赖性的问题,那么你可能会在你的类中引入硬依赖,这首先会破坏整个接口的编码点。

隔离测试很重要。如果您正在测试多个有限的代码,那么这不是单元测试。这是一个集成测试(由于不同的原因,它们有自己的价值)。单元测试可以快速轻松地验证有限的代码块是否按预期工作。当单元测试未通过时,您就知道问题所在,并且不必仔细搜索以找到它。因此,如果单元测试依赖于其他类型,甚至其他系统(可能在这种情况下,DataContext特定于特定数据库),那么我们无法在不触及数据库的情况下测试MyService。这意味着数据库必须处于特定的测试状态,这意味着测试可能不是幂等的(你不能反复运行它并期望相同的结果。)

如需了解更多信息,建议您观看Miguel Castro的Deep Dive into Dependency Injection and Writing Decoupled Quality Code and Testable Software。他提出的最好的观点是,如果你必须使用new来创建一个对象的实例,那么你就会紧密耦合。避免使用new,而依赖注入是一种可以避免它的模式。 (使用new并不总是坏事,我很乐意将new用于POCO模型。)

答案 1 :(得分:0)

您可以手动注入依赖项。然而,这可能是一项非常繁琐的任务。如果您的服务变得更大,您将获得更多依赖项,其中每个依赖项本身可以具有多个依赖项。

如果更改依赖关系,则需要调整所有用法。 DI容器的主要优点之一是容器将完成所有依赖性解析。无需人工操作。只需注册该服务,并随时随地使用它。

对于小型项目来说,这似乎太多开销,但如果你的项目增长一点,你会非常感激。

对于强烈相关且不太可能改变的固定依赖项,可以手动注入它们。

使用DI容器还有另一个优点。 DI容器将控制其服务的生命周期。服务可以是单例,瞬态(每个请求将获得一个新实例)或具有范围的生命周期。

例如,如果您有交易工作流程。范围可以匹配交易。在事务中,对服务的请求将返回相同的实例。

下一个事务将打开一个新范围,因此将获得新实例。 这允许您丢弃或提交一个事务的所有实例,但是阻止后续事务使用前一个事务中的资源。

答案 2 :(得分:0)

你说得对,你可以手动创建所有实例。在小型项目中,这是通常的做法。项目中将类称为组合根的链接的位置,以及构造函数注入

IoC-libraries可以简化此代码,特别是考虑到生命时间范围和组注册等复杂情况。

答案 3 :(得分:0)

反转控制(构建对象)

这种模式的想法是当你想要构造一个对象时,你只需要知道对象的类型,而不是它的依赖关系或参数。

依赖注入

这种模式使控制模式的反转更进一步,使您能够直接将对象注入构造函数中。

同样,您只需要知道要获取的对象的类型,依赖容器将注入一个对象。

您也不需要知道是否构建了新对象,或者您是否已获得现有的参考。

最常用的依赖注入类型是构造函数注入,但您可以将类型注入其他地方,如方法。

Seperation of concerns

通常,您通过接口注册类型以消除对类型的依赖。

这对于mocking types测试非常有帮助,它有助于使用open closed principle

Martin Fowler on "Inversion of Control Containers and the Dependency Injection pattern".