Kotlin:扩展函数的惯用用法 - 将扩展函数放在它扩展的类旁边

问题描述

我在 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)。这个恕我直言仍然不能证明扩展功能是合理的。

对此我得到的反驳是 Official Kotlin Coding Conventions 支持具有这种方式的扩展功能方法。具体:

自由使用扩展函数。每次你有一个主要作用于一个对象的函数时,考虑使它成为一个接受该对象作为接收者的扩展函数Source

还有:

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

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

解决方法

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

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

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

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

,

这是一个相反的论点......

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

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

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

……等等,无休止地。

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

Kotlin 的扩展方法在字节码级别的实现方式完全相同,但它们的语法要简单得多,而且与成员函数完全相同:它们的编写方式相同(使用 this &c),调用它们 看起来就像调用成员函数一样,你的 IDE 会推荐它们。

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

所以:如果扩展函数的主要目的是在成员函数不可能时替代成员函数,那么为什么在成员函数可能时使用它们?!强>

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

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...