3

我在 Kotlin 中看到了一些扩展函数的用法,我个人认为这没有意义,但似乎有一些指南“显然”支持它(解释问题)。

具体来说:在类之外定义扩展函数(但在同一个文件中):

data class AddressDTO(val state: State,
                      val zipCode: String,
                      val city: String,
                      val streetAddress: String
)

fun AddressDTO.asXyzFormat() = "${streetAddress}\n${city}\n${state.name} $zipCode"

广泛使用的地方asXyzFormat(),并且不能被定义为私有/内部(但也适用于可能的情况)。

在我的常识中,如果您拥有代码AddressDTO(班级。

  • 边缘情况:如果您想避免以 - 开头的函数序列化get- 注释类以获得所需的行为(例如@JsonIgnore在函数上)。这个恕我直言仍然不能证明扩展功能是合理的。

我对此的回应是,官方 Kotlin 编码约定支持具有这种方式的扩展功能的方法。具体来说:

自由使用扩展功能。每次您有一个主要作用于对象的函数时,请考虑将其作为扩展函数接受该对象作为接收器。 资源

和:

特别是,当为一个类定义与该类的所有客户端相关的扩展函数时,请将它们放在定义该类本身的同一文件中。在定义仅对特定客户端有意义的扩展函数时,请将它们放在该客户端的代码旁边。不要仅仅为了保存“Foo 的所有扩展名”而创建文件。 资源

我会感谢任何普遍接受的源/参考解释为什么将函数移动为类的成员和/或实用参数支持这种分离更有意义。

4

2 回答 2

5

关于自由使用扩展函数的那句话,我很确定意味着自由地使用它们而不是顶级非扩展函数(而不是使其成为成员函数)。这就是说,如果顶级函数在概念上适用于目标对象,则更喜欢扩展函数形式。

我之前搜索过为什么在处理您拥有源代码的类时可能选择将函数设为扩展函数而不是成员函数的答案,但从未从 JetBrains 找到规范答案。以下是我认为您可能会这样做的一些原因,但有些原因非常受制于意见。

  • 有时您需要一个对具有特定泛型类型的类进行操作的函数。想想List<Int>.sum(),它仅适用于 List 的子集,而不适用于 List 的子类型。
  • 接口可以被认为是合约。接口做某事的函数在概念上可能更有意义,因为它们不是合同的一部分。我认为这是 Iterable 和 Sequence 的大多数标准库扩展函数的基本原理。如果您认为数据类几乎像被动结构,则类似的基本原理可能适用于数据类。
  • 扩展函数提供了允许用户伪覆盖它们的可能性,但迫使他们以独立的方式进行。假设你asXyzFormat()是一个开放的成员函数。在其他一些模块中,您收到 AddressDTO 实例并希望获得它们的 XYZ 格式,完全符合您期望的格式。但是您收到的 AddressDTO可能已被覆盖asXyzFormat()并为您提供了一些意想不到的东西,所以现在您不能信任该功能。如果您使用扩展功能,则允许用户asXyzFormat()用适用于该空间的内容替换他们自己的包中的内容,但您始终可以信任asXyzFormat()源包中的功能。
  • 与接口类似,具有默认实现的成员函数会邀请用户覆盖它。作为接口的作者,您可能需要一个可靠的函数,可以在该接口上使用并具有预期的行为。尽管最终用户可以通过重载将您的扩展隐藏在他们自己的模块中,但这不会影响您自己对该功能的使用。

对于它的价值,我认为当你拥有它的源代码时,选择为类(而不是接口)制作扩展函数是非常罕见的。我想不出标准库中的任何例子。这使我相信编码约定文档在包括接口在内的自由意义上使用了“类”一词。

于 2021-02-10T14:26:41.353 回答
2

这是一个相反的论点……</p>

将扩展函数添加到语言的主要原因之一是能够从标准库、第三方库和其他依赖项中添加功能到您无法控制代码且无法添加成员函数的类(又名方法)。我怀疑这主要是那些编码约定部分正在讨论的情况。

在 Java 中,这种情况下唯一的选择是实用方法:静态方法,通常在一个实用类中收集许多此类方法,每个方法都将相关对象作为其第一个参数:

public static String[] splitOnChar(String str, char separator)
public static boolean isAllDigits(String str)

……等等,没完没了。

主要问题是这些方法很难找到(IDE 没有帮助,除非您已经了解所有各种实用程序类)。此外,调用它们是冗长的(尽管一旦静态导入可用,它就有所改进)。

Kotlin 的扩展方法在字节码级别以完全相同的方式实现,但它们的语法更简单,并且与成员函数完全相同:它们以相同的方式编写(使用this&c),调用它们看起来就像调用成员函数,并且您的 IDE 会建议它们。

(当然,它们也有缺点:没有动态调度,没有继承或覆盖,范围/导入问题,名称冲突,对它们的引用很尴尬,从 Java 访问它们或反射很尴尬,等等。)

所以:如果扩展函数的主要目的是在成员函数不可能的情况下替代成员函数,那么在成员函数可能的情况下你为什么要使用它们呢

(公平地说,您可能需要它们有几个原因。例如,您可以使接收器可以为空,这对于成员函数是不可能的。但在大多数情况下,它们的好处远远超过了适当的成员函数。)

这意味着绝大多数扩展函数可能是为您控制其源代码的类编写的,因此您无法选择将它们放在类旁边

于 2021-02-10T14:23:58.003 回答