什么时候应该返回接口和具体类?

当我用Java编程时,我几乎总是出于习惯,写下这样的东西:

public List foo() { return new ArrayList(); } 

大多数时候甚至没有考虑过它。 现在,问题是:我是否应该始终将接口指定为返回类型? 或者建议使用接口的实际实现,如果是,在什么情况下?

很明显,使用界面有很多优点(这就是它的原因)。 在大多数情况下,库函数使用的具体实现并不重要。 但也许有些情况确实很重要。 例如,如果我知道我将主要访问列表中的数据,则LinkedList会很糟糕。 但是如果我的库函数只返回界面,我根本就不知道。 为了安全起见,我甚至可能需要将列表显式复制到ArrayList

 List bar = foo(); List myList = bar instanceof LinkedList ? new ArrayList(bar) : bar; 

但这似乎很可怕,我的同事可能会在自助餐厅里诽谤我。 理所当然。

你们有什么感想? 您的指导方针是什么?您何时倾向于抽象解决方案,何时会显示您的实施细节以获得潜在的性能提升?

例如,如果我知道我将主要访问列表中的数据,则LinkedList会很糟糕。 但是如果我的库函数只返回界面,我根本就不知道。 为了安全起见,我甚至可能需要将列表显式复制到ArrayList。

正如其他人提到的那样,您不必关心库如何实现function,减少耦合并提高库的可维护性。

如果您作为库客户端可以certificate实现对您的用例表现不佳,那么您可以联系负责人并讨论要遵循的最佳路径(针对此案例的新方法或仅更改实施) 。

也就是说,你的例子充满了过早的优化。

如果该方法是或可能是关键的,它可能会提到文档中的实现细节。

返回适当的接口以隐藏实现细节。 您的客户应该只关心您的对象提供的内容,而不是您如何实现它。 如果您从私有ArrayList开始,并在稍后决定其他内容(例如,LinkedLisk,跳过列表等)更合适,则可以在不返回接口的情况下更改实现而不影响客户端。 返回具体类型的那一刻,机会就会消失。

在OO编程中,我们希望尽可能地封装数据。 尽可能隐藏实际的实现,尽可能地抽象出类型。

在这种情况下,我只会回答有意义的回报 。 将返回值作为具体类是否有意义? 在你的例子中的Aka,问问自己:有人会在foo的返回值上使用特定于LinkedList的方法吗?

  • 如果不是,只需使用更高级别的接口即可。 它更加灵活,允许您更改后端
  • 如果是,请问自己:我不能重构我的代码以返回更高级别的界面吗? 🙂

您的代码越抽象,更改后端时您需要做的更改就越少。 就这么简单。

另一方面,如果你最终将返回值转换为具体类,那么这是一个强烈的迹象,表明你应该返回具体的类。 您的用户/团队成员不应该了解更多或更少的隐式合同:如果您需要使用具体方法,只需返回具体类,以便清楚。

简而言之:代码摘要 ,但显式 🙂

通常,对于面向公众的接口(例如API),在具体实现(例如ArrayList )上返回接口(例如List )会更好。

使用ArrayListLinkedList是库的实现细节,应该考虑该库的最常见用例。 当然,在内部,如果它提供的设施可以使处理更容易,那么使用private方法切换LinkedList并不一定是坏事。

没有理由不在实现中使用具体类,除非有充分的理由相信稍后将使用其他List类。 但话说回来,只要面向公众的部分设计得很好,改变实施细节就不会那么痛苦。

图书馆本身应该是消费者的黑盒子,因此他们不必担心内部会发生什么。 这也意味着应该对库进行设计,使其按照预期的方式使用。

如果没有能够用大量的CS引用来certificate它(我是自学成才的话),在设计课程时,我总是遵循“接受最少派生,回归最多派”的口号,这让我很好这些年。

我想这意味着在接口与具体返回方面,如果你试图减少依赖关系和/或解耦,返回接口通常更有用。 但是,如果具体类实现的不仅仅是那个接口,那么对于方法的调用者来说,通常更有用的是获取具体类(即“最多派生”),而不是将它们无条件地限制为返回对象的function的子集。 – 除非你真的需要限制它们。 然后,您还可以增加界面的覆盖范围。 像这样的不必要的限制我比较无意义的类密封; 你永远不会知道。 只是谈谈该咒语的前一部分(对于其他读者),接受最少派生也为您的方法的调用者提供了最大的灵活性。

-Oisin

作为一项规则,如果我在库的某些私有内部工作中,我只会传回内部实现,即使只是谨慎。 对于所有公共的,可能从我的模块外部调用的东西,我使用接口,以及Factory模式。

以这种方式使用接口已被certificate是编写可重用代码的一种非常可靠的方法。

已经回答了主要问题,您应该始终使用界面。 不过我想评论一下

很明显,使用界面有很多优点(这就是它的原因)。 在大多数情况下,库函数使用的具体实现并不重要。 但也许有些情况确实很重要。 例如,如果我知道我将主要访问列表中的数据,则LinkedList会很糟糕。 但是如果我的库函数只返回界面,我根本就不知道。 为了安全起见,我甚至可能需要将列表显式复制到ArrayList。

如果你要返回的数据结构你知道它的随机访问性能很差 – O(n)并且通常有很多数据 – 你应该指定其他接口而不是List,比如Iterable,这样任何使用该库的人都会充分意识到只有顺序访问可用。

选择正确的类型返回不仅仅是关于接口与具体实现,还有关于选择正确的接口。

API方法是返回接口还是具体类并不重要; 尽管这里的每个人都说过,但一旦代码编写完成,你几乎从不改变实现类。

更重要的是:始终为方法参数使用最小范围接口! 这样,客户端拥有最大的自由度,可以使用您的代码甚至不知道的类。

当一个API方法返回ArrayList ,我绝对没有任何疑虑,但是当它需要一个ArrayList (或者所有常见的Vector )参数时,我会考虑追捕程序员并伤害他,因为这意味着我不能使用Arrays.asList()Collections.singletonList()Collections.EMPTY_LIST

抱歉不同意,但我认为基本规则如下:

  • 对于输入参数,使用最通用的
  • 对于输出值,最具体

因此,在这种情况下,您希望将实现声明为:

 public ArrayList foo() { return new ArrayList(); } 

理由 :输入案例已为每个人所知并解释:使用界面,句点。 但是,输出情况看起来可能违反直觉。 您希望返回实现,因为您希望客户端获得有关接收内容的最多信息。 在这种情况下, 更多的知识就是更多的力量

示例1:客户端想要获取第5个元素:

  • return Collection:必须迭代直到第5个元素vs返回List:
  • return List: list.get(4)

示例2:客户端想要删除第5个元素:

  • return List:必须创建一个没有指定元素的新列表( list.remove()是可选的)。
  • return ArrayList: arrayList.remove(4)

因此,使用接口是一个很大的事实,因为它可以提高可重用性,减少耦合,提高可维护性并使人们满意……但仅在用作输入时

因此,规则可以表述为:

  • 灵活应对您的需求。
  • 提供您所提供的信息。

那么,下次请返回实施。

您使用接口来抽象实际实现。 该接口基本上只是您的实现可以执行的操作的蓝图。

接口是很好的设计,因为它们允许您更改实现细节,而不必担心任何消费者直接受到影响,只要您的实现仍然执行您的界面所说的。

要使用接口,您可以像这样实例化它们:

 IParser parser = new Parser(); 

现在IParser将成为您的界面,而Parser将成为您的实现。 现在,当您使用上面的解析器对象时,您将使用接口(IParser),这反过来将对您的实现(Parser)起作用。

这意味着您可以根据需要更改Parser的内部工作方式,它永远不会影响对您的IParser解析器接口起作用的代码。

通常在所有情况下都使用接口,如果您不需要具体类的function。 请注意,对于列表,Java添加了一个RandomAccess标记类,主要用于区分算法可能需要知道get(i)是否为常量时间的常见情况。

对于代码的使用,上面的迈克尔是正确的,在方法参数中尽可能通用甚至更重要。 在测试这种方法时尤其如此。

您会发现(或已经发现)当您返回接口时,它们会渗透您的代码。 例如,从方法A返回一个接口,然后必须将接口传递给方法B.

你正在做的是通过合同编程,尽管是以有限的方式。

这为您提供了巨大的空间来更改封面下的实现(假设这些新对象满足现有合同/预期行为)。

鉴于所有这些,您在选择实施方面有所裨益,以及如何替代行为(包括测试 – 例如使用模拟)。 如果您没有猜到,我全都赞成这一点,尽量减少(或引入)接口。