发布于:2021-01-25 15:55:21
0
364
0
这是我的基础系列的第一部分。
我觉得我们真正需要回到的一个基本问题是如何使用和理解接口的价值。
在C#和Java这样的语言中,接口是非常常见的,它们的使用比5-10年前要普遍得多。
但我们必须扪心自问的一个问题是,“我们是否正确地使用了它们?““
接口解决了什么问题?
我想让你花点时间,弄清楚你目前是如何使用接口的。
我想让你假装你不知道什么是接口。
准备好了吗?
一个接口试图解决的基本问题是将我们如何使用某个东西与如何实现它分开。
为什么我们要把使用和实现分开?
这样我们就可以编写代码来处理各种不同的职责实现,而不必专门处理每个实现。
更简单地说,这意味着如果我们有一个Driver类,它应该能够有一个方法Drive,可以用来驱动任何汽车、船或其他实现IDriveable接口的类。
Driver类不必为每一类都提供DriveBoat、DriveCar或DriveX方法,这些类支持驱动所需的相同基本操作。
接口试图解决一个非常具体的问题,允许我们根据对象的行为而不是如何与对象交互。
接口是合同
接口允许我们指定特定类满足其他类可以依赖的特定期望。
如果我们有一个实现接口的类,我们可以确定它将支持该接口中定义的所有方法。
乍一看,接口似乎类似于具体的继承,但有一个关键的区别。
具体的继承说Car是一辆汽车,而接口说Car实现了可驱动的接口。
当类实现一个接口时,并不意味着类就是那个接口。因此,完全描述类功能的接口通常是错误的。
一个类可以实现多个接口,因为每个接口只讨论该类能够实现的特定契约。
接口总是由多个类实现的
你可能会说“不,它们不是,我这里有一个类,它有一个其他类都没有实现的接口。”对此我说,“你做错了。”但是,别担心,不止你一个人。我也做错了。我们中的许多人不再正确地使用接口,而是使用它们,因为我们的印象是,我们永远不应该直接使用具体的类。
我们害怕应用程序之间的紧密耦合,所以不管是否需要接口,我们都会为每个类创建接口。
我之所以说接口总是由多个类实现,有一些非常好的理由。
还记得我们说过如何设计接口来解决特定问题吗?
在我的示例中,我讨论了Driver类不必拥有它可以驱动的每种类的方法,而是应该依赖于IDrivable接口,并拥有一个通用的drive方法来驱动实现IDrivable的任何东西。
我们大多数人都接受YAGNI原则,即“你不会需要它。”如果我们只有一个Car类,而没有任何其他类需要由司机类驱动,我们就不需要接口。
在某个时候,我们可能会添加一个Boat类。只有在那个时候,我们才真正有了一个接口将要解决的问题。直到那个时候,添加接口是预期未来要解决的问题。
如果你认为自己擅长预测何时需要接口,我希望你做一个小练习。进入你的代码库,数一数你拥有的所有接口。然后数一数实现这些接口的所有类。我敢打赌这个比率非常接近1:1。
但我该怎么测试呢?如何使用依赖注入?
这两个原因可能是错误使用接口的最合理的原因。
我为创建一个接口而感到内疚,这样我就可以模拟一些东西,我也为我的依赖注入框架创建一个接口而感到内疚,但这并不能使它正确。
在这里,我不能简单地回答您,并且说我可以在没有接口的情况下解决您的单元测试或依赖项注入问题,但是我可以谈谈为什么我们不应该弯曲源代码来适应工具或方法。
我之前谈到过单元测试的目的,其中一个关键的好处是单元测试有助于指导您的设计。单元测试可以帮助我们分离应用程序,并将类整合到单个职责中,这使得尝试和单元测试具有多个依赖项的类非常痛苦。
接口是一种快捷方式,它允许我们摆脱类中的大量依赖关系。
当我们把一个具体类的引用变成一个接口引用时,我们在欺骗系统。我们假装我们的类是解耦的,因为它引用的是一个接口而不是一个具体的类,从而使编写单元测试变得更容易。实际上,它并没有解耦,它实际上更耦合,因为我们的类耦合到一个接口,而这个接口耦合到一个类。我们所做的只是添加间接级别。
依赖注入促进了同样的接口滥用问题。至少它现在在C#和Java中的使用方式是这样的。创建一个接口仅仅是为了能够将该接口的唯一实现注入到类中,这会产生不必要的间接级别,并且不必要地降低类的性能我们的申请。
别误会。依赖注入很好。我会把细节留到另一篇文章中,但我相信依赖注入的真正好处是当它用于控制使用哪个接口实现时,而不是当一个接口只有一个实现时。
归根结底,我无法给你一个好的答案,即如何在不滥用接口的情况下进行单元测试或使用依赖注入。我认为你可以通过选择拆分类并实际减少依赖性来减少滥用,而不是简单地创建一个接口并将其注入类中,但你仍然会遇到问题一辆车有一个引擎,如果你想单元测试这辆车,你要么要用真正的引擎,要么想办法模仿它。
这里的关键问题是接口是语言的一部分,但是单元测试和依赖注入不是。我们正试图通过使用技巧使它们与语言相适应。技巧是我们创建一个接口来提供类之间的接缝。问题是我们这样做稀释了接口的效力。我们真正需要的是一个语言支持的接缝,让我们能够轻松地替换具体类在运行时的实现。
作者介绍