如何在这个密封的 class 层次结构中摆脱这个样板代码?

How to get rid of this boilerplate code in this sealed class hierarchy?

假设我有一个这样的 sealed class 层次结构:

sealed class A {
    abstract val x: Int
    abstract fun copyX(x1: Int): A
}

data class A1(override val x: Int, val s1: String) : A() {
    override fun copyX(x1: Int): A {
        return this.copy(x = x1)
    }
}

data class A2(override val x: Int, val s2: String) : A() {
    override fun copyX(x1: Int): A {
        return this.copy(x = x1)
    }
}

所有数据 类 都有字段 x 并且应该提供方法 copyX(x1: Int) 来复制除 x 之外的所有字段并用 [= 覆盖 x 18=]。例如,

fun foo(a: A): A { a.copyX(100) }

上面的定义可能有效,但在所有数据 类 中重复 copyX 似乎非常笨拙。您建议如何摆脱这种重复的 copyX

首先,您可以将 copyX 作为扩展(甚至 A 的成员)实现,以便将代码集中在一个地方,至少避免重复 copyX密封 class 子类型中的函数:

sealed class A {
    abstract val x: Int
}

fun A.copyX(x1: Int): A = when (this) {
    is A1 -> copy(x = x1)
    is A2 -> copy(x = x1) 
}

data class A1(override val x: Int, val s1: String) : A()

data class A2(override val x: Int, val s2: String) : A()

如果你有很多密封的子类型并且它们都是 data class 或有一个 copy 函数,你也可以用反射来复制它们。为此,您需要从 KClass 中获取 primaryConstructor 或名为 copy 的函数,然后填写调用的参数,按名称查找 x 参数,然后为它放置 x1 值,并放置从 component1()component2() 等调用中获得的值或为其他参数保留默认值。它看起来像这样:

fun A.copyX(x1: Int): A {
    val copyFunction = this::class.memberFunctions.single { it.name == "copy" }
    val args = mapOf(
        copyFunction.instanceParameter!! to this,
        copyFunction.parameters.single { it.name == "x" } to x1
    )
    return copyFunction.callBy(args) as A
}

这是可行的,因为 callBy 允许省略可选参数。

请注意,它需要依赖于 kotlin-reflect,并且仅适用于 Kotlin/JVM。此外,反射有一些性能开销,因此它不适合性能关键代码。您可以通过使用 Java 反射(this::class.javagetMethod(...))来优化这一点(这会更冗长)并缓存反射实体。