我见过代码,其中每个类都有一个它实现的接口。
有时候他们都没有共同的界面。
它们就在那里,它们被用来代替具体的物体。
它们不为两个类提供通用接口,并且特定于该类解决的问题域。
有没有理由这样做?
答案 0 :(得分:39)
没有
接口适用于具有复杂行为的类,如果您希望能够创建该接口的模拟或伪实现类以用于单元测试,那么它们特别方便。
但是,有些类没有很多行为,可以更像是值,通常由一组数据字段组成。为这样的类创建接口没有什么意义,因为这样做会在模拟或提供接口的替代实现时没有什么意义上引入不必要的开销。例如,考虑一个类:
class Coordinate
{
public Coordinate( int x, int y);
public int X { get; }
public int y { get; }
}
您不太可能希望界面ICoordinate
与此类一起使用,因为除了简单地获取和设置X
和Y
值之外,以任何其他方式实现它都没什么意义
但是,班级
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
界面后面。
答案 1 :(得分:26)
在重新审视这个答案之后,我决定稍微修改一下。
不,为每个类提取接口不是最佳做法。这实际上可能适得其反。但是,接口很有用,原因如下:
为了实现这些目标,接口被视为良好做法(并且实际上是最后一点所必需的)。根据项目大小,您会发现您可能永远不需要与接口通信,或者由于上述原因之一而不断提取接口。
我们维持一个大型应用程序,其中一些部分是伟大的,一些正在遭受缺乏关注。我们经常发现自己重构将接口拉出类型以使其可测试,或者我们可以更改实现,同时减少该更改的影响。如果您对公共API不严格,接口只能代表公共API,那么我们也这样做是为了减少具体类型可能意外强加的“耦合”效应,因此对我们来说本身就变得非常严格。
也就是说,可以在没有接口的情况下抽象行为,并且可以在不需要接口的情况下测试类型,因此它们不是上面的要求。只是您可以用来支持这些任务的大多数框架/库将有效地对抗接口。
<小时/> 我将把我的旧答案留给上下文。
接口定义公共合同。实现接口的人必须实现此合同。消费者只看到公共合同。这意味着实施细节已从消费者那里抽象出。
这些天的直接用途是 单元测试 。接口很容易模拟,存根,假,你可以命名。
另一个直接用途是 依赖注入 。给定接口的注册具体类型被提供给消费接口的类型。该类型并不特别关注实现,因此它可以抽象地询问接口。这允许您在不影响大量代码的情况下更改实现(只要合同保持不变,影响区域就非常小)。
对于非常小的项目,我倾向于不打扰,对于中型项目,我倾向于打扰重要的核心项目,而对于大型项目,几乎每个类都有一个界面。这几乎总是支持测试,但在某些情况下注入行为或行为抽象以减少代码重复。
答案 2 :(得分:8)
让我引用OO大师Martin Fowler,为这个帖子中最常见的答案添加一些可靠的理由。
此摘录来自“企业应用程序架构模式”(参加“编程经典”和\或“每个开发必读”书籍类别)。
[Pattern] 分隔界面
(...)
何时使用
当您需要破坏系统的两个部分之间的依赖关系时,可以使用分离接口。
(...)
我遇到过很多开发人员,他们为每个写的课程都有单独的界面。我认为这是过分的,特别是对于 应用开发。保持单独的接口和 实现是额外的工作,特别是因为你经常需要工厂 类(带接口和实现)也是如此。对于 应用程序我建议仅在您需要时使用单独的界面 打破一个依赖或你想拥有多个独立 实现。如果你把接口和实现 在一起并需要稍后将它们分开,这是一个简单的重构 可以延迟,直到你需要这样做。
回答你的问题:没有
我自己也看到过这种类型的“花哨”代码,开发人员认为这些代码是SOLID,但却难以理解,难以扩展且过于复杂。
答案 3 :(得分:5)
为项目中的每个类提取接口背后没有实际原因。这是一个过度杀戮。他们必须提取接口的原因是他们似乎实现了OOAD原则“Program to Interface, not to Implementation”。您可以通过示例here找到有关此原则的更多信息。
答案 4 :(得分:4)
将接口和编码添加到接口使得更换实现变得更容易。这也适用于单元测试。如果您正在测试一些使用该接口的代码,您可以(理论上)使用模拟对象而不是具体对象。这使您的测试更集中,更细粒度。
从我所看到的更常见的是在实际生产代码中切换测试(模拟)的实现。是的,这是单位测试的意思。
答案 5 :(得分:2)
这可能看起来很愚蠢,但这样做的潜在好处是,如果在某些时候你意识到有更好的方法来实现某个功能,你可以编写一个实现相同接口的新类,并改变一行使您的所有代码都使用该类:分配接口变量的行。
这样做(编写一个实现相同接口的新类)也意味着您可以随时在新旧实现之间来回切换以进行比较。
最终您可能永远不会利用这种便利,而您的最终产品确实只使用为每个界面编写的原始类。如果是这样的话,太好了!但它真的没有花太多时间来编写这些界面,如果你需要它们,它们会为你节省很多时间。
答案 6 :(得分:2)
我觉得每个班级都不合理。
这是一个关于你期望从什么类型的组件重用多少的问题。当然,你必须计划更多的重用(不需要在以后进行重大的重构)而不是你现在真正要使用的,但为程序中的每个类提取抽象接口意味着你的类比需要。
答案 7 :(得分:2)
我喜欢可以以两种不同的方式实现的接口,无论是在时间上还是在空间中,即将来可能以不同的方式实现,或者在不同的部分中有2个不同的代码客户端可能需要不同实现的代码。
您的代码的原始编写者可能只是机器人编码,或者他们是聪明的并准备版本弹性,或预备单元测试。更有可能是前者,因为版本弹性是一种不常见的需求 - (即,部署客户端且无法更改,并且将部署必须与现有客户端兼容的组件)
我喜欢依赖于与我计划测试的其他代码隔离的依赖项的接口。如果没有创建这些接口来支持单元测试,那么我不确定它们是不是一个好主意。接口需要维护成本,并且当需要将对象与另一个对象交换时,您可能希望将接口仅应用于少数几个方法(因此更多类可以实现接口),使用抽象可能更好class(以便可以在继承树中实现默认行为)。
所以预先需要的界面可能不是一个好主意。
答案 8 :(得分:1)
如果是Dependency Inversion原则的一部分。基本上代码取决于接口而不是实现。
这使您可以轻松地交换实现,而不会影响调用类。它允许更松散的耦合,这使得系统的维护更加容易。
随着您的系统不断发展并变得越来越复杂,这一原则越来越有意义!
答案 9 :(得分:1)
如果您希望将来能够确保能够注入其他实现,可能会有。对于一些(可能是大多数)情况,这是过度的,但它与大多数习惯一样 - 如果你已经习惯了,你就不会花太多时间去做。而且由于您永远无法确定将来要替换的 ,因此在每个类中提取接口确实有一定的意义。
问题永远不会有一个解决方案。因此,同一个接口总是可以有多个实现。
答案 10 :(得分:1)
接口很好,因为你可以在(单元)测试时模拟类。
我为至少所有触及外部资源的类(例如数据库,文件系统,webservice)创建接口,然后编写模拟或使用模拟框架来模拟行为。
答案 11 :(得分:0)
接口定义行为。如果实现一个或多个接口,则对象的行为类似于描述的一个或其他接口。这允许类之间的松散耦合。当您必须用另一个实现替换实现时,它非常有用。类之间的通信应始终使用接口来完成,除非这些类彼此紧密绑定。
答案 12 :(得分:0)
为什么需要接口?实际思考和深刻思考。接口并不真正附加到类,而是附加到服务。接口的目标是允许其他人使用您的代码而不为代码提供服务。所以它与服务及其管理有关。
见你