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

n3r*_*3rd 22 java collections abstraction

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

public List<String> foo() {
    return new ArrayList<String>();
}
Run Code Online (Sandbox Code Playgroud)

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

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

List bar = foo();
List myList = bar instanceof LinkedList ? new ArrayList(bar) : bar;
Run Code Online (Sandbox Code Playgroud)

但这看起来很可怕,我的同事可能会在自助餐厅里骂我.理所当然.

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

duf*_*ymo 30

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

  • 我会全心全意地同意这一点。但是,如果有人使用性能很重要的低级 API,那么透露一些实现细节可能是一件好事,尽管您显然会失去灵活性和抽象性。我希望那些真正不得不在现实世界的应用程序中进行这种权衡的人可以分享一些他或她的想法。 (2认同)

Vin*_*vic 9

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

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

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

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

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


小智 8

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

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

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

public ArrayList<String> foo() {
  return new ArrayList<String>();
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 返回集合:必须迭代到第 5 个元素与返回列表:
  • 返回列表: list.get(4)

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

  • 返回列表:必须创建一个没有指定元素的新列表(list.remove()可选)。
  • 返回数组列表: arrayList.remove(4)

因此,使用接口很棒是一个重要的事实,因为它促进了可重用性,减少了耦合,提高了可维护性并使人们感到高兴……但仅当用作输入时

所以,同样,规则可以表述为:

  • 对你提供的东西保持灵活。
  • 对您提供的内容提供信息。

所以,下次,请返回执行。


x0n*_*x0n 7

如果没有能够用大量的CS引语来证明这一点(我自学成才),在设计课程时,我总是遵循"接受最少派生,回归最多"的口号,这让我很好这些年.

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

-Oisin

  • 你的意思是你应该总是返回具体的实现吗?我不同意这一点,因为您在代码的使用者和方法之间创建了不必要的耦合.返回接口会强制使用者根据抽象合同处理返回的对象,而不是具体的implmentation - 可能有其他方法. (3认同)
  • 根据您的编辑,我会说我们肯定不同意.允许调用者访问界面之外的其他方法(除了内部使用之外),将您锁定到特定实现中,应该避免(内部使用除外). (3认同)
  • 我认为这里存在中间立场 - 这取决于 API 的方法、可见性、用例和受众。我原则上同意你的观点,但我不同意绝对的说法。我不知道,膝跳反应。 (2认同)

Nic*_*zet 7

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

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

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

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

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

简而言之:代码抽象,但显式 :)


coo*_*ird 6

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

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

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

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


Mic*_*rdt 5

API 方法是否返回接口或具体类并不重要;不管这里的每个人都怎么说,一旦编写了代码,您几乎永远不会更改实现类。

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

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