【问题标题】:How use all subclasses of a sealed class to handle a subclass instance?如何使用密封类的所有子类来处理子类实例?
【发布时间】:2019-10-29 22:03:09
【问题描述】:

我正在尝试在实践中使用 Kotlin 中的新 sealedSubclasses 功能。我需要从一个密封的类中处理一些子类的实例,但是我以前不知道哪个子类是,所以正常的过程是尝试所有的子类。

假设有 20 个或更多子类。

在下面,我对其进行了很多简化以使其易于理解。我只使用了 2 个带有标量内部类型的子类。在现实世界中可以是复杂的对象类型。

sealed class Person {
  abstract var name:String
     // ...
}

data class Doctor(      
  var license: String = "",   
    // ....
  override var name:String = ""
) : Person()

// ....
data class Fireman ( 
  var medals: Int=0, 
  // ....   
  override var name:String = ""
) : Person()

现在我需要克隆该结构:首先尝试:

var p:Person = Doctor("123","Louis")
var q:Person
  // ...
Person.copy  // it is forbidden

所以我需要这样做

var p:Person = Doctor("123","Louis")
var q:Person
when (p) {
is Doctor -> q = p.copy()   
   // ...
is Fireman -> q = p.copy()
}   

注意这是错误的代码,因为我总是添加一个新的子类,我必须在克隆过程中手动添加,重复子块。

所以我尝试使用sealedSubclasses,但我没有将子类与类型连接以模拟前面的代码

for (Token::class.sealedSubclasses 中的子类) { if (ele::class.simpleName as String== derived.qualifiedName){ // ?????? } }

我能做什么?

【问题讨论】:

    标签: kotlin


    【解决方案1】:

    如果你愿意,可以通过反射来实现,但我认为sealedSubclasses 没有用处。大约(未经测试):

    fun copy(p: Person, name: String) {
       val copyMethod = p::class.memberFunctions().find { it.name == "copy" }!!
       val params = copyMethod.parameters
       val receiverParam = params[0]
       val nameParam = params.find { it.name == "name" }!!
       return copyMethod.callBy(mapOf(receiverParam to p, nameParam to name))
    }
    

    当然,反射不安全和性能的一般警告也适用。

    【讨论】:

    • 真诚地,与其陷入那种复杂性,我几乎更喜欢这种糟糕的解决方案,而不是必须在我的代码中手动编写所有子类,并对密封的类定义发表评论以永远不会忘记它。
    • 在我正在做的那种涉及树和图的应用程序中,密封类对我有很大帮助。如果我的代码变得混乱,我不能放弃它。我用来制作可以假设许多类似但不相等类型的通用节点。这是一种联合类型。
    【解决方案2】:

    您的问题的一个解决方案是创建您自己的抽象copy() 方法的实现:

    sealed class Person {
        abstract val name: String
    }
    
    data class Doctor(
        override val name: String,
        val license: String,
        val extraProperty: Any
    ) : Person()
    
    data class Fireman(
        override var name: String,
        val medals: Int
    ) : Person()
    
    val p: Person = Doctor("123", "Louis", "extra")
    var q: Person = when (p) {
        is Doctor -> p.copy(name = "New name", license = "Other license", extraProperty = "extra2")
        is Fireman -> p.copy(name = "New Name", medals = 15)
        // And so on...
    }
    

    有人在这篇帖子https://stackoverflow.com/a/47624138/3542143中遇到了和你类似的问题

    【讨论】:

    • 我以前见过,但我认为这是一个笨拙的解决方案。我必须在我的 conde 中创建的每个密封类中重复它。
    • 你是对的,它是重复的,为了避免它,我更新了代码,删除了 abstract fun copy(name: String): Person 方法,因为 when(p) 块推断 DoctorFireman 类型,所以现在我们可以使用默认的copy 方法。这是有效的,因为我覆盖了 name 属性和继承类的特定属性。
    【解决方案3】:

    我认为,如果您使用空参数调用copy(),您可以使用泛型。 (在我的手机上,所以请原谅这里的任何语法问题。)

    class Person <out T: Person> {
        abstract var name: String
        abstract fun unchangedCopy(): T
    }
    
    data class Unemployed (
        override var name: String
    ): Person<Unemployed>() {
        override fun unchangedCopy() = copy()
    }
    
    var p:Person = Unemployed(“John”)
    var q:Person = p.unchangedCopy()
    

    这将迫使您在每个新的子类中覆盖 unchangedCopy,因此没有什么可忘记的。

    【讨论】:

    • 在抽象类中,我尝试添加abstract fun &lt;T&gt;uCopy(p:T): T 并且此代码在子类override fun &lt;Doctor&gt;uCopy(p:Doctor ):Doctor = p.copy() 但如果在copy 调用中给出一个奇怪的错误,甚至通常允许在数据中类。
    • 如果你只使用fun uCopy(p:Doctor ):Doctor = p.copy(),没有错误,但不要覆盖任何内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    相关资源
    最近更新 更多