Menu
CoddyTech

动态分派

本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 27 课,共 57 课。

多态表示一次调用可以有多种表现方式。通过超类或接口类型调用被重写的方法时,Kotlin 会在程序运行时查看对象的实际类,并执行该类的版本。这就是动态分派:

abstract class Animal(val name: String) {
    abstract fun sound(): String
    fun speak() = "$name: ${sound()}"
}
class Dog(name: String) : Animal(name) {
    override fun sound() = "Woof"
}
class Cow(name: String) : Animal(name) {
    override fun sound() = "Moo"
}

在 main 中:

val barn: List<Animal> = listOf(Dog("Rex"), Cow("Bess"), Dog("Fido"))
for (a in barn) println(a.speak())

输出:

Rex: Woof
Bess: Moo
Fido: Woof

当出现新的子类时,调用代码不会改变。下面的 totalArea 是为任意 Shape 编写的,因此它也适用于 Triangle,尽管没有人修改它:

interface Shape {
    fun area(): Double
}
class Square(val side: Double) : Shape {
    override fun area() = side * side
}
class Triangle(val base: Double, val height: Double) : Shape {
    override fun area() = base * height / 2
}

fun totalArea(shapes: List<Shape>) = shapes.sumOf { it.area() }

在 main 中:

println(totalArea(listOf(Square(2.0), Triangle(3.0, 4.0))))

输出:

10.0

分派查看的是对象,而不是变量。将另一个对象赋给同一个变量,会改变下次调用时运行的代码:

interface Mode {
    fun format(text: String): String
}
class Normal : Mode {
    override fun format(text: String) = text
}
class Shout : Mode {
    override fun format(text: String) = text.uppercase() + "!"
}

在 main 中:

var mode: Mode = Normal()
println(mode.format("hello"))
mode = Shout()
println(mode.format("hello"))

输出:

hello
HELLO!

多态可以替代 if (kind == "dog") 这样的检查,否则每当使用动物时都要重复进行这些检查。每个类都保留自己的行为,添加一种动物就意味着添加一个类:

// 没有多态:每个函数都要检查类型
fun sound(kind: String) = when (kind) { "dog" -> "Woof"; "cow" -> "Moo"; else -> "?" }

// 有了多态:新类型自带自己的行为
class Sheep(name: String) : Animal(name) {
    override fun sound() = "Baa"
}
challenge icon

挑战

简单

Character(name, hp) 是一个开放类。takeHit(damage) 会从 hp 中减去伤害(最低不会低于 0),而 status() 会返回 Ada: 30 hp,或者在生命值为 0 时返回 Ada: down。你可以在任何地方读取 hp,但只能在 Character 内修改它。通过重写 takeHit 并将调整后的伤害传给 super.takeHit,完成这些子类:Warrior(40 点生命值)受到的伤害减半,向下取整;Mage(25 点生命值)有一个护盾,可以吸收第一次受到的全部攻击;Rogue(30 点生命值)会躲避它受到的每第二次攻击(第 2 次、第 4 次,依此类推)。

提供的代码会按 warrior:Ada,mage:Bo,rogue:Cy 读取队伍,然后执行 all 10 或 Ada 12 攻击。它会通过 Character 类型调用 takeHit,最后打印每个角色的 status()。

你的代码应写在 Character.kt、Warrior.kt、Mage.kt 和 Rogue.kt 中。Main.kt 包含提供的输入/输出代码,不能编辑。

亲手试试

fun main() {
    // 提供的输入输出代码:保持原样。
    val input = generateSequence(::readLine).toList()
    val party = input[0].split(",").map { spec ->
        val (kind, name) = spec.split(":")
        when (kind) {
            "warrior" -> Warrior(name)
            "mage" -> Mage(name)
            else -> Rogue(name)
        }
    }
    for (cmd in input.drop(1)) {
        val (target, damage) = cmd.split(" ")
        for (c in party) {
            if (target == "all" || target == c.name) c.takeHit(damage.toInt())
        }
    }
    for (c in party) println(c.status())
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Kotlin 在线编辑器