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

Mic*_*ael 7 kotlin sealed-class

假设我有一个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)
    }
}
Run Code Online (Sandbox Code Playgroud)

所有的数据类有现场x,并应提供方法copyX(x1: Int)来复制所有字段,但x并覆盖xx1。例如,

fun foo(a: A): A { a.copyX(100) }
Run Code Online (Sandbox Code Playgroud)

上面的定义可能有用,但是copyX在所有数据类中重复似乎很笨拙。您如何建议消除这种重复copyX

hot*_*key 5

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

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()
Run Code Online (Sandbox Code Playgroud)

如果您有很多密封的子类型,并且所有子类型都是data类或具有一个copy函数,则也可以使用反射来泛型地复制它们。对于这一点,你需要获得primaryConstructor或命名的函数copyKClass,然后填写为调用的参数,寻找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
}
Run Code Online (Sandbox Code Playgroud)

之所以callBy可行,是因为省略了可选参数。

请注意,它需要依赖kotlin-reflect并且只能与Kotlin / JVM一起使用。同样,反射也有一些性能开销,因此不适合于性能关键的代码。您可以改为使用Java反射(this::class.javagetMethod(...))(这会更冗长)并缓存反射实体来优化此效果。