模拟提问和依赖注入

4

我理解依赖注入,但还没有那种让我彻底明白的瞬间。

为什么要使用DI?此外,当模拟使用文件系统等对象时,模拟对象能够做什么?它只是进行虚假调用(因此并没有真正使用文件系统)吗?

4个回答

3
DI的目的是使代码松耦合。根据定义,松散耦合对于单元测试是必需的,因为如果许多类紧密耦合,则不再是单元测试(而是集成测试)。
然而,DI的目的并不是为了启用单元测试,而是为了使您的代码库更易维护。其中许多积极的副作用之一是它也变得极易测试。
当涉及模拟文件系统时,过于紧密地模拟文件系统的方面基本上是一个坏主意,因为这会导致漏洞抽象。相反,您应该考虑使用流或类似概念。

2

依赖注入是一种不将依赖项硬编码到组件中的做法。例如:

class Service {
   Collaborator c = new Collaborator()
}

该伪代码中的协作者已经硬编码,难以更改。如果您需要更换协作者,则需要修改代码。

class Service {
    Collaborator c;

    Service(Collaborator c) {
       this.c = c;
    }
}

现在,您可以通过构造函数将所需的协作者“注入”到服务组件中。没有硬编码的依赖关系。
这是很好的,因为您可以轻松地替换协作者的实现。您的代码现在是“松耦合”的——没有特定实现的硬依赖,只有类型和行为的依赖。
其中一个应用是,您现在可以通过在测试中注入模拟协作者来测试服务,以便以不依赖于协作者的方式测试所有服务功能。
实际上,您希望Collaborator成为接口(或任何您选择的语言支持的等效物),以便您可以定义其行为,并将实现留给实际注入的实例。
关于模拟执行文件操作的协作者的第二部分问题是正确的。如果您模拟文件系统协作者,则可以在不实际访问文件系统的情况下测试使用协作者的内容。

1

让我从hvgotcodes answer再深入几步:

class Service {
   Collaborator c = new Collaborator()
}

这是您的原始类,其中包含硬编码的依赖关系。

class Service {
    Collaborator c;

    Service(Collaborator c) {
       this.c = c;
    }
}

这是一个与注入依赖项有关的新潮类。

到目前为止,一切都很好。现在,让我们从Collaborator中提取一个接口;将其称为ICollaborator。现在你的新式类看起来像这样:

class Service {
    ICollaborator c;

    Service(ICollaborator c) {
       this.c = c;
    }
}

这对你有什么好处呢?在你的代码中,你可以创建这个类来像第一个例子一样运行:
// w00t!  My code compiles and works again!  Ship it!
Service myService = new Service(new Collaborator());

非常简单明了。美妙之处在于当您想使用不同类型的Collaborator时,甚至是模拟或伪造的时候。只要它实现了ICollaborator接口,您就可以放心使用:

// I'm using Fake It Easy for this example.
Service myService = new Service(A.Fake<ICollaborator>());

太好了!现在你拥有了一个可单元测试的Service实例,它不会将具体的Collaborator一起带上(这将破坏真正的“单元”测试)。


我明白了。我理解这对于类似日期时间的东西来说是有益的。如果日期时间从接口中派生,而我从同一接口创建了另一个类,那么这些方法可以为空存根吗? - GurdeepS
没错!现在,任何带有返回类型的方法都应该返回该方法合理的内容,但是void方法可以完全为空存根。 - Jesse C. Slicer

1

为了更深入地讨论...

当人们谈论依赖注入时,大多数时候的主要争论点都是可测试性,但正如 Mark Seeman 指出的那样(顺便说一句,购买他关于 DI 的书籍真的很棒并且非常启发人,抱歉打广告),其最重要的方面是使您的应用程序松耦合,从而更易于维护。

为了以其他答案中显示的相同代码提供示例:

假设您有一个新需求,根据......我不知道......年份的时间,您需要使用不同的协作者,您可以像下面这样做:

ICollaborator collaborator;

switch(timeOfYear)  
{  
    case "Spring":  
        collaborator = new SpringCollaborator();  
        break;  
    case "Summer":  
        collaborator = new SummerCollaborator();  
        break;  
    case "Fall":  
        collaborator = new FallCollaborator();  
        break;  
    case "Winter":  
        collaborator = new WinterCollaborator();  
        break;  
}  

Service myService = new Service(collaborator);

通过这种方式,您可以创建尽可能多的实现,并且只要它实现了ICollaborator接口,您的服务就不需要更改,因为它不关心协作者的详细信息。

DI还有很多内容,但是松散耦合和可测试性始终是首先指出的两个好处。

谢谢。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接