代码之家  ›  专栏  ›  技术社区  ›  n3rd

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

  •  19
  • n3rd  · 技术社区  · 15 年前

    在Java编程时,我几乎总是习惯性地写下这样的东西:

    public List<String> foo() {
        return new ArrayList<String>();
    }
    

    大多数时候都没有考虑过。现在,问题是:我应该 总是 指定接口作为返回类型?还是建议使用接口的实际实现,如果是,在什么情况下?

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

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

    但这似乎很可怕,我的同事可能会在自助餐厅里用私刑把我处死。正是如此。

    你们觉得怎么样?您的指导原则是什么,您什么时候倾向于抽象解决方案,以及您什么时候揭示实现的细节以获得潜在的性能提升?

    12 回复  |  直到 10 年前
        1
  •  8
  •   Vinko Vrsalovic    15 年前

    例如,如果我知道我会 主要访问列表中的数据 随机地,一个LinkedList会很糟糕。 但如果我的图书馆只起作用 返回接口,我只是不返回 知道。为了安全起见,我可能 甚至需要显式复制列表 到一个阵列列表。

    正如其他人所提到的,您不必关心库是如何实现功能的,以减少耦合和增加库的可维护性。

    如果您作为一个库客户机,能够证明实现对您的用例的性能不好,那么您可以联系负责人,讨论要遵循的最佳路径(此用例的新方法或只是更改实现)。

    也就是说,您的示例充满了过早优化的味道。

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

        2
  •  29
  •   duffymo    15 年前

    返回适当的接口以隐藏实现详细信息。您的客户机应该只关心您的对象提供了什么,而不是您如何实现它。如果您从一个私有的arraylist开始,稍后再决定其他一些东西(如linkedlisk、skip list等)更合适,那么如果您返回接口,您可以在不影响客户端的情况下更改实现。当你返回一个具体类型时,机会就消失了。

        3
  •  7
  •   Nicolas Dumazet    15 年前

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

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

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

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

    另一方面,如果您最终将返回值强制转换为具体类,那么这是一个很强的迹象,表明您可能应该返回具体类而不是具体类。你的用户/队友不必知道多少隐含的契约:如果你需要使用具体的方法,为了清晰起见,只需返回具体的类。

    简而言之:代码 摘要 但是 明确地 :)

        4
  •  6
  •   coobird    15 年前

    通常,对于面向公共的接口(如API),返回接口(如 List )具体实施(如 ArrayList )会更好。

    A的使用 数组列表 LinkedList 是该库的一个实现细节,对于该库最常见的用例,应该考虑它。当然,在内部, private 方法移交 链表 如果它提供了使处理更容易的设施,那么就不一定是件坏事。

    没有理由不在实现中使用具体的类,除非有充分的理由相信 稍后将使用类。但是,再次重申,只要面向公众的部分设计良好,更改实现细节就不应该像这样痛苦。

    对于消费者来说,图书馆本身应该是一个黑匣子,因此他们不必担心内部的情况。这也意味着图书馆的设计应该使其按照预期的方式使用。

        5
  •  6
  •   x0n    15 年前

    在设计课程的时候,我总是信奉“接受最少派生的,返回最派生的”这句咒语,而这些年来,这句话一直让我很受欢迎。

    我想这意味着,就接口和具体返回而言,如果您试图减少依赖性和/或去耦,返回接口通常更有用。但是,如果具体类实现 更多 对于方法的调用方来说,将具体类(即“最派生的”)返回通常比返回该接口更有用,而不是将它们限制在返回对象功能的一个子集上,除非实际上 需要 限制他们。同样,您也可以增加接口的覆盖范围。像这样不必要的限制,我把它比作轻率地封闭类;你永远不会知道。只需稍微讨论一下这句咒语的前一部分(对于其他读者而言),接受派生最少的部分也可以为方法的调用者提供最大的灵活性。

    -奥辛

        6
  •  4
  •   Mario Ortegón    15 年前

    通常,如果我在一个库的一些私有的、内部的工作中,我只会返回内部实现,即使这样也很谨慎。对于所有公共的、可能从模块外部调用的东西,我都使用接口,以及工厂模式。

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

        7
  •  4
  •   Kris    15 年前

    主要问题已经回答,您应该始终使用该接口。不过,我想对

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

    如果您返回的数据结构具有较差的随机访问性能(O(N),并且通常是大量数据),那么您应该指定其他接口而不是列表,例如iterable,这样使用库的任何人都将完全意识到只有顺序访问可用。

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

        8
  •  3
  •   Michael Borgwardt    15 年前

    不管API方法是返回接口还是返回具体的类,这都无关紧要;尽管这里的每个人都这么说,但一旦编写了代码,您几乎永远不会更改实现类。

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

    当API方法返回时 ArrayList 我完全不怀疑,但当它要求 数组列表 (或者,所有人都有共同点, Vector )参数,我考虑搜寻程序员并伤害他,因为这意味着我不能使用 Arrays.asList() , Collections.singletonList() Collections.EMPTY_LIST .

        9
  •  2
  •   user3830747    10 年前

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

    • 为了 输入 参数使用最多 通用的 .
    • 为了 输出 价值观,最 具体的 .

    因此,在本例中,您希望将实现声明为:

    public ArrayList<String> foo() {
      return new ArrayList<String>();
    }
    

    理论基础 : 输入案例已经被所有人知道并解释:使用接口,句点。然而,输出情况看起来可能与直觉相反。 您希望返回实现,因为您希望客户机拥有关于接收内容的最多信息。在这种情况下, 知识越多权力越大 .

    示例1:客户希望获得第5个元素:

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

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

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

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

    因此,同样,规则可以表述为:

    • 对你提供的东西要灵活。
    • 对你所提供的内容要有信息性。

    所以,下次,请返回实现。

        10
  •  1
  •   Alex    15 年前

    您可以使用接口从实际实现中抽象出来。接口基本上只是实现可以做什么的蓝图。

    接口是很好的设计,因为它们允许您更改实现细节,而不必担心它的任何使用者都会受到直接影响,只要您的实现仍然按照接口所说的做。

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

    IParser parser = new Parser();
    

    现在,iparser将是您的接口,解析器将是您的实现。现在,当您从上面处理解析器对象时,您将处理接口(iparser),而该接口反过来又将处理您的实现(解析器)。

    这意味着您可以随心所欲地更改解析器的内部工作方式,它不会影响针对您的iparser解析器接口工作的代码。

        11
  •  1
  •   Kathy Van Stone    15 年前

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

    对于代码的使用,上面的michael是对的,在方法参数中尽可能地通用通常更重要。这在测试这种方法时尤其如此。

        12
  •  0
  •   Brian Agnew    15 年前

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

    你所做的是按合同编程,尽管方式有限。

    这为您提供了很大的范围来更改覆盖下的实现(前提是这些新对象满足现有合同/预期行为)。

    考虑到所有这些,您在选择实现以及如何替换行为(例如,测试-使用模拟)方面都有好处。如果你没有猜到,我都支持这个,并尽可能减少到(或引入)接口。