【问题标题】:How to get rid of this boilerplate code in this sealed class hierarchy?如何在这个密封的类层次结构中摆脱这个样板代码?
【发布时间】:2019-12-31 08:58:59
【问题描述】:

假设我有一个像这样的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之外的所有字段,并用x1覆盖x。例如,

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

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

【问题讨论】:

  • 请问您在现实生活中的用例是什么?过去我也遇到过类似的问题,我通常要么重复这样的声明,要么找到完整的其他方式。看起来您想通过使用这个密封类来抽象子类,但您仍然希望在复制时保留子类的实现,所以我想知道您的需求是什么。
  • @Joffrey 恐怕实际用例现在描述起来有点太复杂了。谢谢你的提问。

标签: kotlin sealed-class


【解决方案1】:

首先,您可以将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()

如果你有很多密封的子类型并且它们都是data 类或有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(...))(这会更冗长)并缓存反射实体来对此进行优化。

【讨论】:

  • 我不喜欢运行时反射,所以我可能不会使用它,但无论如何感谢您的详细回答:))
猜你喜欢
  • 2012-02-05
  • 1970-01-01
  • 2021-07-07
  • 1970-01-01
  • 1970-01-01
  • 2016-10-02
  • 2012-10-08
  • 1970-01-01
  • 2021-01-27
相关资源
最近更新 更多