0

我有这样的Fruit课。

open class Fruit(var taste: String) {

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

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

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类中覆盖以下方法:

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

要正确覆盖此方法,我需要传入 Fruit 类的实例(如在 super 方法中)。如果我这样做,我将始终必须检查Fruit实例是否实际上是Apple实例。但是,它不应该只与前者一起工作,因为AppleextendsFruit吗?

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

有什么好方法可以做到这一点?

4

1 回答 1

2

While technical alternatives have been suggested in comments, I'd like to add another perspective. What we're seeing here is a class design problem which comes up when attempting to use inheritance for anything other than a true generalization/specialization relationship.

The example declares:

  1. Each Fruit must be able to consume another Fruit.
  2. An Apple is a kind of Fruit.

Then the idea is:

  • An Apple must not consume any kind of Fruit, but an Apple only.

If an Apple were really a Fruit, it would fully adhere to the Fruit's declaration and be able to consume another Fruit of any kind. As the intended apple Apple violates rule 1, is not really a Fruit and the language prevents you from declaring it as such.

Trying to work around this (e.g. via runtime checks in overridden methods) masquerades the underlying problem and introduces surprises to those using such classes.

Solution:

  • Use inheritance for true generalization/specialization relationships only. If it is 100% certain, no strings attached, that an apple is a fruit, inheritance is a perfect fit. Otherwise it is not.
  • In this case: Rethink the intended semantics:
    • What's the real meaning of consume?
    • Is there a notion of a fruit consuming an arbitrary (potentially incompatible specialization of another) fruit?
    • Or is it rather individual specializations of fruit which each have their own independent notion of consuming? Then there would be no common consume method at the Fruit level.

Copying Derived Classes via References To A Base Class

Answering the additional question in the comment:

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

I'd rather not express SweetFruit and SourFruit as specializations of a CoreFruit. Flavors such as sweet and sour are traits of a fruit and better expressed as properties.

But I could extend your example a bit and then suggest a class design which includes a clone() function providing a deep copy functionality on a base class Flavor. Note that the output shows different hash codes for cloned objects:

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")
}
于 2020-08-28T15:56:27.103 回答