如何使用超类型参数覆盖方法
How to override a method with supertype-argument
我有一个Fruit
class这样的
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 关系以外的任何事物时,就会出现这个问题。
示例声明:
- 每个
Fruit
必须能够 consume
另一个 Fruit
。
- 一个
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?
我不想将 SweetFruit
和 SourFruit
表达为 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")
}
我有一个Fruit
class这样的
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 关系以外的任何事物时,就会出现这个问题。
示例声明:
- 每个
Fruit
必须能够consume
另一个Fruit
。 - 一个
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?
我不想将 SweetFruit
和 SourFruit
表达为 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")
}