4

我正在阅读 Code Complete,在本书的最后,在关于重构的章节中,作者列出了一些你应该做的事情,以在重构时提高代码的质量。

他的观点之一是始终尽可能返回特定类型的数据,尤其是在返回集合、迭代器等时。所以,正如我所理解的那样,如果你使用那种数据类型,而不是返回,比如说Collection<String>,你应该返回HashSet<String>方法里面。

这让我很困惑,因为这听起来像是他在鼓励人们打破信息隐藏的规则。现在,我在谈论访问器时理解了这一点,这是一个明确的案例。但是,在计算和修改数据时,方法的抽象级别意味着没有直接的数据结构,我发现最好尽可能返回抽象的数据类型,只要数据不分崩离析(我不会例如,返回Object而不是Iterable<String>)。

所以,我的问题是: Code Complete 的建议背后是否有更深层次的哲学,即始终返回尽可能具体的数据类型,并允许向下转换,而不是保持我刚刚不理解的需要知道的基础?

4

6 回答 6

3

我认为在大多数情况下这是完全错误的。它必须是: 尽可能宽容,根据需要尽可能具体

在我看来,您应该始终返回 List 而不是 LinkedList 或 ArrayList,因为区别更多的是实现细节而不是语义细节。来自Java 的 Google collections api 的人更进一步:他们返回(并期望)迭代器,这已经足够了。但是,他们还建议在可能的情况下返回 ImmutableList、-Set、-Map 等,以向调用者显示他不必制作防御性副本。

除此之外,我认为不同列表实现的性能并不是大多数应用程序的瓶颈。

于 2009-05-25T07:56:26.170 回答
1

找不到任何证据来证实我的主张,但想法/指南似乎是:

接受输入时尽可能宽容。选择通用类型而不是专用类型。这意味着客户可以将您的方法用于不同的专业类型。因此,将 IEnumerable 或 IList 作为输入参数意味着该方法可以运行 ArrayList 或 ListItemCollection。它最大限度地提高了您的方法有用的机会。

返回值时尽可能严格。如果可能,请首选专用类型。这意味着客户端不必事后猜测或跳过箍来处理返回值。此外,专门的类型具有更大的功能。如果您选择返回 IList 或 IEnumerable,调用者可以使用您的返回值执行的操作数量会大大减少 - 例如,如果您通过 ArrayList 返回 IList,要获取返回的元素数量 - 使用 Count 属性,客户必须垂头丧气。但是这样的向下转换会破坏目的-今天有效..明天不会(如果您更改返回对象的类型)。因此,出于所有目的,客户无法轻松获得元素的数量 - 导致他编写平凡的样板代码(在多个地方或作为辅助方法)

这里的总结是它取决于上下文(大多数规则的例外)。例如,如果您的返回值最可能的用途是客户端将使用返回的列表来搜索某个元素,那么返回支持某种搜索方法的列表实现(类型)是有意义的。让客户端尽可能容易地使用返回值。

于 2009-05-25T07:15:57.267 回答
1

大多数时候应该返回一个接口,或者可能是一个代表返回值的抽象类型。如果要返回 X 列表,请使用 List。如果需要返回列表类型,这最终会提供最大的灵活性。

也许后来你意识到你想要返回一个链表或一个只读列表等。如果你把一个具体的类型你卡住了,改变它会很痛苦。使用接口解决了这个问题。

@Gishu

如果您的 api 要求客户在大多数情况下直接转换,那么您的设计就很糟糕。如果客户需要转换为 Y,为什么还要麻烦返回 X。

于 2009-05-25T07:28:15.447 回答
0

我可以看到,在某些情况下,返回更具体的数据类型会很有用。例如,知道返回值是 LinkedList 而不仅仅是 List 将允许您从列表中删除,因为知道它会很有效。

于 2009-05-25T07:02:09.587 回答
0

我认为,在设计接口时,您应该设计一种方法来返回尽可能抽象的数据类型。返回特定类型将使方法的目的更清楚地了解它们返回的内容。

另外,我会这样理解:

返回尽可能抽象的数据类型 = 返回尽可能具体的数据类型

即当你的方法应该返回任何集合数据类型时返回集合而不是对象。

告诉我我是否错了。

于 2009-05-25T07:23:10.310 回答
0

特定的返回类型更有价值,因为它:

  1. 减少通过投射或反射发现功能时可能出现的性能问题
  2. 增加代码可读性
  3. 事实上,并没有超出必要的范围。

函数的返回类型是专门为满足其所有调用者而选择的。调用函数应该尽可能抽象地使用返回变量,因为调用函数知道如何使用数据。

只需要遍历结构吗?有必要对结构进行排序吗?改造它?克隆吗?这些是只有调用者才能回答的问题,因此可以使用抽象类型。被调用的函数必须提供所有这些情况。

事实上,如果您现在最具体的用例是 Iterable<string>,那很好。但通常情况下——你的调用者最终需要有更多的细节,所以从一个特定的返回类型开始——它不需要任何成本。

于 2009-05-25T07:23:27.610 回答