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