多态性
多态性 是一个古希腊术语,意为“多种形式”。在编程中,多态性意味着一个对象或其成员具有多个实现。
考虑一个简单的 Pet 类型的层次结构。Pet 类表示所有的宠物都可以 speak()(说话)。Dog 和 Cat 覆盖了 speak() 成员函数:
// Polymorphism/Pet.kt
package polymorphism
import atomictest.eq
open class Pet {
open fun speak() = "Pet"
}
class Dog : Pet() {
override fun speak() = "Bark!"
}
class Cat : Pet() {
override fun speak() = "Meow"
}
fun talk(pet: Pet) = pet.speak()
fun main() {
talk(Dog()) eq "Bark!" // [1]
talk(Cat()) eq "Meow" // [2]
}
注意 talk() 函数的参数。当将 Dog 或 Cat 传递给 talk() 时,具体类型被遗忘,变成了一个普通的 Pet —— 既 Dog 和 Cat 都被向上转型为 Pet。现在,这些对象被视为普通的 Pet,那么行 [1] 和 [2] 中的输出应该都是 "Pet",对吗?
talk() 不知道它接收到的 Pet 的确切类型。尽管如此,在通过基类 Pet 的引用调用 speak() 时,会调用正确的子类实现,并获得所需的行为。
多态性发生在父类引用包含子类实例时。当您在父类引用上调用成员时,多态性会从子类中产生正确的重写成员。
将函数调用与函数体连接在一起称为绑定。通常情况下,您不会对绑定多想,因为它在编译时静态地发生。在多态性中,相同的操作必须对不同的类型产生不同的行为 —— 但编译器无法提前知道要使用哪个函数体。函数体必须在运行时动态确定,使用动态绑定。动态绑定也称为晚期绑定或动态调度。只有在运行时,Kotlin 才能确定要调用的确切 speak() 函数。因此,我们说多态性调用 pet.speak() 的绑定是动态发生的。
考虑一个幻想游戏。游戏中的每个 Character 都有一个 name,并且可以 play()。我们将 Fighter 和 Magician 结合起来来构建特定的角色:
// Polymorphism/FantasyGame.kt
package polymorphism
import atomictest.*
abstract class Character(val name: String) {
abstract fun play(): String
}
interface Fighter {
fun fight() = "Fight!"
}
interface Magician {
fun doMagic() = "Magic!"
}
class Warrior :
Character("Warrior"), Fighter {
override fun play() = fight()
}
open class Elf(name: String = "Elf") :
Character(name), Magician {
override fun play() = doMagic()
}
class FightingElf :
Elf("FightingElf"), Fighter {
override fun play() =
super.play() + fight()
}
fun Character.playTurn() = // [1]
trace(name + ": " + play()) // [2]
fun main() {
val characters: List<Character> = listOf(
Warrior(), Elf(), FightingElf()
)
characters.forEach { it.playTurn() } // [3]
trace eq """
Warrior: Fight!
Elf: Magic!
FightingElf: Magic!Fight!
"""
}
在 main() 中,每个对象在放入 List 时都会向上转型为 Character。trace 显示了在 List 中的每个 Character 上调用 playTurn() 会产生不同的输出。
playTurn() 是基类 Character 上的扩展函数。当在行 [3] 中调用它时,它是静态绑定的,这意味着要调用的确切函数在编译时确定。在行 [3] 中,编译器确定只有一个 playTurn() 函数实现 —— 就是在行 [1] 上定义的那个。
当编译器分析行 [2] 中的 play() 函数调用时,它不知道要使用哪个函数实现。如果 Character 是一个 Elf,它必须调用 Elf 的 play()。如果 Character 是一个 FightingElf,它必须调用 FightingElf 的 play()。它还可能需要调用一个尚未定义的子类函数。函数绑定在每次调用时都会有所不同。在编译时,唯一确定的是行 [2] 中的 play() 是 Character 子类的成员函数。具体的子类只能在运行时根据实际的 Character 类型来确定。
- -
动态绑定是不是免费的。决定运行时类型的额外逻辑会对性能产生轻微的影响,与静态绑定相比。为了强制清晰,Kotlin 默认为封闭的类和成员函数。要继承和重写,必须明确指定。
像 when 语句这样的语言特性可以独立学习。多态性不可以 —— 它只在协调中工作,作为类关系的更大画面的一部分。为了有效地使用面向对象的技术,您必须扩展您的视角,不仅包括个体类的成员,还包括类之间的共性以及它们相互之间的关系。
***练
习和解答可以在 www.AtomicKotlin.com 找到。***