繁体   English   中英

为每个类提取接口是最佳做法吗?

[英]Is it the best practice to extract an interface for every class?

我见过代码,其中每个类都有一个它实现的接口。

有时它们并没有共同的界面。

它们就在那里,它们被用来代替具体的物体。

它们不为两个类提供通用接口,并且特定于该类解决的问题域。

有什么理由这样做吗?

没有。

接口适用于具有复杂行为的类,如果您希望能够创建该接口的模拟或伪实现类以用于单元测试,则特别方便。

但是,有些类没有很多行为,可以更像是值,通常由一组数据字段组成。 为这样的类创建接口没有什么意义,因为这样做会在模拟或提供接口的替代实现时没有什么意义上引入不必要的开销。 例如,考虑一个类:

class Coordinate
{
  public Coordinate( int x, int y);
  public int X { get; }
  public int y { get; }
}

您不太可能希望接口ICoordinate与此类一起使用,因为除了简单地获取和设置XY值之外,以任何其他方式实现它都没什么意义。

但是,班级

class RoutePlanner
{
   // Return a new list of coordinates ordered to be the shortest route that
   // can be taken through all of the passed in coordinates.
   public List<Coordinate> GetShortestRoute( List<Coordinate> waypoints );
}

你可能会想一个IRoutePlanner的接口RoutePlanner ,因为有可能被用于规划路线许多不同的算法。

另外,如果你有第三堂课:

class RobotTank
{
   public RobotTank( IRoutePlanner );
   public void DriveRoute( List<Coordinate> points );
}

通过为RoutePlanner一个接口,您可以为RobotTank编写一个测试方法,该方法使用模拟RoutePlanner创建一个只返回无特定顺序的坐标列表的方法。 这将允许测试方法检查罐在坐标之间正确导航而不测试路线规划器。 这意味着您可以编写仅测试一个单元(油箱)的测试,而无需测试路线规划器。

你会看到,很容易将真正的坐标输入这样的测试而不需要将它们隐藏在ICoordinate接口之后。

重新回答这个问题之后,我决定稍微修改一下。

不,为每个类提取接口不是最佳实践。 这实际上可能适得其反。 但是,接口很有用,原因如下:

  • 测试支持(模拟,存根)。
  • 实现抽象(进一步到IoC / DI)。
  • 辅助事物,如C#中的co-contra和contra-variance支持。

为了实现这些目标,接口被认为是良好的实践(并且最后一点实际上是必需的)。 根据项目大小,您会发现您可能永远不需要与接口通信,或者由于上述原因之一而不断提取接口。

我们维持一个大型应用程序,其中一些部分很好,一些部分正在遭受缺乏关注。 我们经常发现自己重构将接口拉出类型以使其可测试,或者我们可以更改实现,同时减少该更改的影响。 我们还这样做是为了减少具体类型可能会在您的公共API不严格的情况下意外强加的“耦合”效应(接口只能代表公共API,因此对我们来说固有地变得非常严格)。

也就是说,可以在没有接口的情况下抽象行为,并且可以在不需要接口的情况下测试类型,因此它们不是上述要求 只是您可以用来支持这些任务的大多数框架/库将有效地对抗接口。


我将留下我的旧答案的背景。

接口定义了公共合同。 实现接口的人必须实现此合同。 消费者只看到公共合同。 这意味着实现细节已经从消费者中抽象出来。

这些天的直接用途是单元测试 接口很容易模拟,存根,假,你的名字。

另一个直接用途是依赖注入 给定接口的注册具体类型被提供给消费接口的类型。 该类型并不特别关注实现,因此它可以抽象地询问接口。 这允许您在不影响大量代码的情况下更改实现(只要合同保持不变,影响区域就非常小)。

对于非常小的项目,我倾向于不打扰,对于中型项目,我倾向于打扰重要的核心项目,而对于大型项目,几乎每个类都有一个界面。 这几乎总是支持测试,但在某些情况下注入行为或行为抽象以减少代码重复。

让我引用OO大师Martin Fowler为这个帖子中最常见的答案添加一些可靠的理由。

此摘录来自“企业应用程序架构模式” (参与“编程经典”和“或每个开发必读”书籍类别)。

[Pattern] 分离界面

(......)

何时使用它

当您需要破坏系统的两个部分之间的依赖关系时,可以使用分离的接口。

(......)

我遇到过许多开发人员,他们为每个编写的课程都有单独的接口。 我认为这是过度的,特别是对于应用程序开发。 保持单独的接口和实现是额外的工作,尤其是因为您经常需要工厂类(具有接口和实现)。 对于应用程序,我建议仅在您想要破坏依赖关系或希望拥有多个独立实现时才使用单独的接口。 如果将接口和实现放在一起并需要稍后将它们分开,这是一个简单的重构,可以延迟直到你需要这样做。

回答你的问题:没有

我自己看过这种类型的“花哨”代码,开发人员认为他是SOLID,但却难以理解,难以扩展且过于复杂。

为项目中的每个类提取接口背后没有实际原因。 这是一个过度杀戮。 它们必须提取接口的原因是它们似乎实现了OOAD原则“ 程序到接口,而不是实现 ”。 您可以在此处找到有关此原理的更多信息。

将界面和编码添加到界面使得更换实现变得更容易。 这也适用于单元测试。 如果您正在测试一些使用该接口的代码,您可以(理论上)使用模拟对象而不是具体对象。 这使您的测试更集中,更细粒度。

从我所看到的更常见的是在实际生产代码中切换测试(模拟)的实现。 是的,这是单元测试的意思。

这可能看起来很愚蠢,但这样做的潜在好处是,如果在某些时候你意识到有更好的方法来实现某个功能,你可以编写一个实现相同接口的新类,并将一行更改为使所有代码都使用该类:分配接口变量的行。

这样做(编写一个实现相同接口的新类)也意味着您可以始终在新旧实现之间来回切换以进行比较。

最终你可能永远不会利用这种便利,而你的最终产品确实只使用为每个界面编写的原始类。 如果是这样的话,太好了! 但它真的没有花太多时间来编写这些接口,如果你需要它们,它们会为你节省很多时间。

我觉得每个班级都不合理。

这是一个问题,你期望从什么类型的组件重用多少。 当然,你必须计划更多的重用(不需要在以后进行重大的重构)而不是你现在真正要使用的,但为程序中的每个类提取抽象接口意味着你的类比需要。

我喜欢可以通过两种不同的方式实现的接口,无论是在时间还是空间,即它可以在将来以不同的方式实现,或者在代码的不同部分中有2个不同的代码客户端可能需要不同的实现。

您的代码的原始编写者可能只是机器人编码,或者他们是聪明的并准备版本弹性,或预备单元测试。 更可能是前者,因为版本弹性是一种不常见的需求 - (即,部署客户端并且无法更改,并且将部署必须与现有客户端兼容的组件)

我喜欢与我计划测试的其他代码隔离的依赖项的接口。 如果没有创建这些接口来支持单元测试,那么我不确定它们是不是一个好主意。 接口需要维护成本,并且当需要将对象与另一个对象交换时,您可能希望将接口仅应用于少数几个方法(因此更多类可以实现接口),使用抽象可能更好class(以便可以在继承树中实现默认行为)。

所以预先需要的接口可能不是一个好主意。

如果是依赖性倒置原则的一部分。 基本上代码取决于接口而不是实现。

这使您可以轻松地交换实现,而不会影响调用类。 它允许更松散的耦合,这使得系统的维护更加容易。

随着您的系统不断发展并变得越来越复杂,这一原则越来越有意义!

如果你想确保能够在将来注入其他实现,可能会有。 对于一些(可能是大多数)情况,这是过度的,但它与大多数习惯一样 - 如果你已经习惯了,你就不会花太多时间去做。 而且因为你不能确定你会希望在将来取代,提取每个类的接口确实有一点什么

问题永远不会只有一个解决方案。 因此,同一接口总是可以有多个实现。

接口很好,因为你可以在(单元)测试时模拟类。

我为至少所有接触外部资源的类(例如数据库,文件系统,webservice)创建接口,然后编写模拟或使用模拟框架来模拟行为。

接口定义了一种行为。 如果实现一个或多个接口,则对象的行为类似于描述的一个或其他接口。 这允许类之间的松散耦合。 当您必须用另一个实现替换实现时,它非常有用。 类之间的通信应始终使用接口来完成,除非类彼此紧密绑定。

为什么需要接口? 实际思考和深刻思考。 接口并不真正附加到类,而是附加到服务。 接口的目标是允许其他人使用您的代码而不为代码提供服务。 因此它涉及服务及其管理。

拜拜

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM