Dependency injection 模拟问题和依赖注入

Dependency injection 模拟问题和依赖注入,dependency-injection,Dependency Injection,我理解依赖注入,但还没有“啊”的那一刻,它点击了一下,我真的看到了曙光 我为什么要使用DI?另外,当模拟使用文件系统的对象时,模拟对象能够做什么?它是否只是进行虚拟调用(因此并不真正使用文件系统)?依赖项注入只是不将依赖项硬编码到组件中的一种实践。比如说 class Service { Collaborator c = new Collaborator() } 该伪代码将协作者硬编码。很难改变。如果是的话 class Service { Collaborator c;

我理解依赖注入,但还没有“啊”的那一刻,它点击了一下,我真的看到了曙光


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

依赖项注入只是不将依赖项硬编码到组件中的一种实践。比如说

class Service {
   Collaborator c = new Collaborator()
}
该伪代码将协作者硬编码。很难改变。如果是的话

class Service {
    Collaborator c;

    Service(Collaborator c) {
       this.c = c;
    }
}
现在,您可以通过构造函数将所需的协作者“注入”到服务组件中。没有硬编码的依赖项

class Service {
    Collaborator c;

    Service(Collaborator c) {
       this.c = c;
    }
}
这很好,所以您可以轻松地交换协作器的实现。您的代码现在是“松散耦合的”——对特定实现没有硬依赖,只对类型和行为有硬依赖

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

实际上,您希望
Collaborator
成为一个接口(或您选择的语言支持的任何等效接口),以便您可以定义行为,并将实现留给您注入的实际实例


问题的第二部分,关于模拟执行文件操作的协作者,是正确的。如果模拟文件系统协作器,则可以单独测试使用协作器的内容,而不会实际影响文件系统。DI的目的是使代码松散耦合。根据定义,单元测试需要松散耦合,因为如果许多类紧密耦合,那么它就不再是单元测试(而是集成测试)

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


在模拟文件系统时,将文件系统的各个方面镜像得太近基本上是一个坏主意,因为这将导致。相反,你应该考虑使用流或类似的概念。

让我再向前走几步:

是具有硬编码依赖项的原始类

class Service {
    Collaborator c;

    Service(Collaborator c) {
       this.c = c;
    }
}
是具有注入依赖项的新颖类

class Service {
    Collaborator c;

    Service(Collaborator c) {
       this.c = c;
    }
}
到目前为止,一切顺利。现在,让我们使用
Collaborator
并从中提取一个接口;称之为ICollaborator。现在,您的newerfangled类如下所示:

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());
非常容易切割和干燥。当你想使用一种不同类型的
合作者时,这种美就来了,甚至可能是一种模仿或伪造的合作者。只要它实现了
ICollaborator
接口,您就是黄金:

// I'm using Fake It Easy for this example.
Service myService = new Service(A.Fake<ICollaborator>());
//我在这个例子中使用了Fake It Easy。
服务myService=新服务(A.Fake());

瞧!您现在有了一个可单元测试的
服务
实例,它不会将具体的
合作者
拖到一起(这将打破真正的“单元”测试)。

为讨论添加更多内容

大多数时候,当人们谈论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还有很多,但松散耦合和可测试性始终是最先指出的两个优点


问候。

我明白了。我知道这是datetime的一个优点。如果datetime派生自一个接口,并且我从同一接口创建了另一个类,那么这些方法可以是空存根吗?准确地说!现在,任何具有返回类型的方法都应该为该方法返回合理的内容,但是void方法可以是完全空的存根。