代码之家  ›  专栏  ›  技术社区  ›  phi oa

有没有办法在Kotlin中实现静态多态性?

  •  0
  • phi oa  · 技术社区  · 2 年前

    我遇到过这样一种情况,我得到了一些共享同一接口的类:

    sealed interface Animal {
        // "const static val nickname: String" ??
    }
    
    class Canidae: Animal {
        companion object {
            const val nickname = "dog"
        }
    }
    
    class Felidae: Animal {
        companion object {
            const val nickname = "cat"
        }
    }
    
    // and more ...
    

    我想做一个函数,它是这样工作的:

    inline fun<reified T: Animal, R> Animal.doSomething(f: (T) -> R): R =
        if (this is T) {
            f(this)
        } else {
            error("it's not a ${T.nickname}, it's a ${this.nickname}!") // but this simply can't compile
        }
    

    我可以保证所有实现的类 Animal 有一个编译时常数 nickname 。是否存在的方法 doSomething 在Kotlin?

    我试着只使用反射并枚举的每个派生类 动物 ,而且它确实奏效了。但这会使运行时资源消耗过多,而且维护所有这些都很累 昵称 s在单个函数中,而不是在定义它关联的类的位置。如果有一种更优雅的方式,那将是非常有帮助的。

    1 回复  |  直到 2 年前
        1
  •  0
  •   Joffrey    2 年前

    Kotlin没有静态的概念,但它有伴随对象。由于伴随对象是对象,所以可以使它们实现接口。

    您可以通过以下方法(诚然是过度设计)来利用这一点:

    • 使用不同的类型表示的族 Animal (或者任何更适合你的术语,但犬科和猫科动物 家庭 根据我30秒的谷歌搜索:D)。
    • 制作 动物 子类将其族宣传为属性,因此您可以访问的族 this 在您的功能中。
    • 使每个动物类的伴侣对象实现 Family 接口,因此您可以在代码中使用类名来引用任何给定的族 动物 子类(您可以创建正则 object s表示每个家庭,但使用同伴可以指代使用类名的家庭)

    这给出了以下内容:

    sealed interface Animal {
        val family: Family<*>
    }
    
    interface Family<T : Animal> {
        val nickname: String
    }
    
    class Canidae: Animal {
        
        override val family = Canidae
        
        companion object : Family<Canidae> {
            override val nickname = "dog"
        }
    }
    
    class Felidae: Animal {
        
        override val family = Felidae
        
        companion object : Family<Felidae> {
            override val nickname = "cat"
        }
    }
    
    inline fun <reified T : Animal, R> Animal.doSomething(family: Family<T>, f: (T) -> R): R =
        if (this is T) {
            f(this)
        } else {
            error("it's not a ${family.nickname}, it's a ${this.family.nickname}!")
        }
    
    

    你可以这样使用它:

    animal.doSomething(Felidae) { println(it) }
    

    不过,这种实现有一个缺点。你可能会犯错误 Canidae 类公开 Felidae 家族没有编译错误。

    我们可以通过制作 动物 类递归泛型:

    sealed interface Animal<T : Animal<T>> {
        val family: Family<T>
    }
    
    interface Family<T : Animal<T>> {
        val nickname: String
    }
    
    class Canidae: Animal<Canidae> {
        
        override val family = Canidae
        
        companion object : Family<Canidae> {
            override val nickname = "dog"
        }
    }
    
    class Felidae: Animal<Felidae> {
        
        override val family = Felidae
        
        companion object : Family<Felidae> {
            override val nickname = "cat"
        }
    }
    
    inline fun <reified T : Animal<T>, R> Animal<*>.doSomething(family: Family<T>, f: (T) -> R): R =
        if (this is T) {
            f(this)
        } else {
            error("it's not a ${family.nickname}, it's a ${this.family.nickname}!")
        }