我理解依赖注入,但还没有那种让我彻底明白的瞬间。
为什么要使用DI?此外,当模拟使用文件系统等对象时,模拟对象能够做什么?它只是进行虚假调用(因此并没有真正使用文件系统)吗?
我理解依赖注入,但还没有那种让我彻底明白的瞬间。
为什么要使用DI?此外,当模拟使用文件系统等对象时,模拟对象能够做什么?它只是进行虚假调用(因此并没有真正使用文件系统)吗?
依赖注入是一种不将依赖项硬编码到组件中的做法。例如:
class Service {
Collaborator c = new Collaborator()
}
该伪代码中的协作者已经硬编码,难以更改。如果您需要更换协作者,则需要修改代码。
class Service {
Collaborator c;
Service(Collaborator c) {
this.c = c;
}
}
让我从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
一起带上(这将破坏真正的“单元”测试)。
为了更深入地讨论...
当人们谈论依赖注入时,大多数时候的主要争论点都是可测试性,但正如 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还有很多内容,但是松散耦合和可测试性始终是首先指出的两个好处。
谢谢。