如何使用超类型参数覆盖方法

How to override a method with supertype-argument

我有一个Fruitclass这样的

open class Fruit(var taste: String) {

    open fun consume(from: Fruit) {
        taste = from.taste
    }
}

我有一个 Apple class 像这样扩展 Fruit class。

class Apple(
    var color: String,
    taste: String
): Fruit(taste) {

    // caution: method overrides nothing
    override fun consume(from: Apple) {
        super.consume(from)
        color = from.color
    }
}

这是我的使用代码:

val fruit: Fruit = Apple(color = "green", taste = "sweet")
val badFruit = Apple(
    color = anyOf("red", "blue", "golden"),
    taste = anyOf("sour+", "sweet+", "chilli+")
)

fruit.consume(from = badFruit)

println("BadFruit: $badFruit")
println("InfectedFruit: $fruit")

问题:

我无法覆盖 Apple class 中的以下方法:

override fun consume(from: Apple) {
    super.consume(from)
    color = from.color
}

为了正确覆盖这个方法,我需要传入一个 Fruit 的实例 class(就像在 super 方法中一样)。如果我这样做,我将始终必须检查 Fruit 实例是否实际上是 Apple 实例。但是,它不应该只适用于前者,因为 Apple extends Fruit?

我怎样才能实现这样的功能,当我在 fruit: Fruit = Apple(...) 上调用 consume() 时,它实际上调用了 Apple#consume() 方法?

执行此操作的好方法是什么?

虽然评论中提出了技术替代方案,但我想补充另一个观点。我们在这里看到的是一个 class 设计问题,当试图将继承用于真正的 generalization/specialization 关系以外的任何事物时,就会出现这个问题。

示例声明:

  1. 每个 Fruit 必须能够 consume 另一个 Fruit
  2. 一个Apple是一种Fruit

那么思路是:

  • Apple 不能 consume 任何类型的 Fruit,只能是 Apple

如果 Apple 真的是 Fruit,它将完全遵守 Fruit 的声明并能够 consume 另一个 Fruit任何形式的。由于预期的苹果 Apple 违反了规则 1,因此实际上并不是 Fruit 并且语言阻止您这样声明它。

试图解决这个问题(例如通过运行时检查覆盖的方法)伪装了潜在的问题并给那些使用这种 classes 的人带来了惊喜。

解决方案:

  • 仅对真正的 generalization/specialization 关系使用继承。如果 100% 确定 一个苹果 是一个 水果 ,那么继承是一个完美的选择。否则不是。
  • 在这种情况下:重新考虑预期的语义:
    • consume的真正含义是什么?
    • 是否有水果消耗任意(可能与另一个水果不兼容的特化)水果的概念?
    • 或者说是水果的特化,每个都有自己独立的消费观念?那么Fruit级别就没有通用的consume方法了。

通过对基数的引用复制派生的 Classes Class

回答 中的附加问题:

how can I make sure this will copy properties of both SourFruit and CoreFruit?

我不想将 SweetFruitSourFruit 表达为 CoreFruit 的特化。酸甜等味道是水果的特征,最好用属性来表达。

但我可以稍微扩展您的示例,然后建议 class 设计,其中包括 clone() 函数,在基础 class Flavor 上提供深层复制功能.请注意,输出显示了克隆对象的不同哈希码:

data class Fruit(var weight: Double, var flavors: MutableList<Flavor>) {
    fun clone(): Fruit {
        return Fruit(weight, flavors.map { it.clone() }.toMutableList())
    }
}

abstract class Flavor {
    abstract fun clone(): Flavor
}

class SweetFlavor(var intensity: Int, var isHealthy: Boolean) : Flavor() {
    override fun clone(): Flavor {
        return SweetFlavor(intensity, isHealthy)
    }
}

class SourFlavor(var intensity: Int) : Flavor() {
    override fun clone(): Flavor {
        return SourFlavor(intensity)
    }
}

fun main() {
    val apple = Fruit(0.2, mutableListOf(SweetFlavor(4, true), SourFlavor(2)))
    val lemon = Fruit(0.35, mutableListOf(SourFlavor(9)))
    val appleClone = apple.clone()

    println("apple: $apple")
    println("lemon: $lemon")
    println("appleClone: $appleClone")

    appleClone.weight += 0.5
    appleClone.flavors[0] = SweetFlavor(6, false)

    println("apple: $apple")
    println("appleClone: $appleClone")
}