嘲弄问题和依赖注入



我理解依赖注入,但没有"啊"的时刻,当它点击时,我真正看到了光明。

我为什么要使用DI?另外,当模拟像那些使用文件系统的对象时,模拟对象能够做什么?它是否只是执行虚拟调用(因此并不真正使用文件系统)?

DI的目的是使代码松散耦合。根据定义,单元测试需要松耦合,因为如果许多类是紧密耦合的,那么它就不再是单元测试(而是集成测试)。

然而,DI的目的并不是启用单元测试,而是使您的代码库更易于维护。许多积极的副作用之一是,它也变得更加可测试。

在模拟文件系统时,过于紧密地镜像文件系统的各个方面基本上是一个坏主意,因为这将导致漏抽象。相反,您应该考虑使用流或类似的概念。

依赖注入就是不把依赖硬编码到组件中的做法。例如

class Service {
   Collaborator c = new Collaborator()
}

伪代码是合作者硬编码的。这很难改变。如果你做了

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

现在你可以通过构造函数把想要的合作者"注入"到Service组件中。没有硬编码的依赖项。

这很好,因此您可以轻松地交换合作者的实现。你的代码现在是"松耦合的"——没有对特定实现的硬依赖,只有对类型和行为的依赖。

这样做的一个应用是,您现在可以通过在测试中注入模拟合作者来测试Service,这样您就可以以一种不依赖于合作者的方式测试所有的服务功能。

在实践中,您希望Collaborator是一个接口(或者您所选择的语言支持的任何等价的接口),以便您可以定义行为,并将实现留给您注入的实际实例。

你问题的第二部分,关于嘲笑一个做文件操作的合作者,是正确的。如果模拟文件系统协作器,则可以在不实际触及文件系统

的情况下,单独测试使用协作器的内容。

让我从hvgotcodes的答案再往前走几步:

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(这会破坏真正的"单元"测试)。

为了进一步讨论…

大多数时候,当人们谈论DI时,主要的论点将沿着可测试性的思路,但是,正如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的内容,但松散耦合和可测试性总是首先指出的两个好处。

问候。

最新更新