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:
- Each
Fruit
must be able to consume
another Fruit
.
- 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")
}