Menu
Coddy logo textTech

Динамическая диспетчеризация

Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 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"), которые в противном случае повторялись бы везде, где используются животные. Каждый класс хранит собственное поведение, а добавление нового вида животного означает добавление одного класса:

// без полиморфизма: каждая функция проверяет kind
fun sound(kind: String) = when (kind) { "dog" -> "Woof"; "cow" -> "Moo"; else -> "?" }

// с полиморфизмом: новый kind приносит своё собственное поведение
class Sheep(name: String) : Animal(name) {
    override fun sound() = "Baa"
}
challenge icon

Задание

Легко

Character(name, hp) — открытый класс. takeHit(damage) вычитает величину урона из hp (значение не может быть меньше 0), а status() возвращает Ada: 30 hp или Ada: down при значении 0. hp можно читать отовсюду, но изменять его можно только внутри Character. Завершите подклассы, переопределив takeHit и передав изменённый урон в super.takeHit: Warrior (40 hp) получает половину урона, округлённую вниз; у Mage (25 hp) есть щит, который поглощает весь первый удар; Rogue (30 hp) уклоняется от каждого второго полученного удара (2-го, 4-го и т. д.).

Предоставленный код считывает отряд в формате warrior:Ada,mage:Bo,rogue:Cy, а затем наносит удары all 10 или Ada 12. Он вызывает takeHit через тип Character и в конце выводит каждый результат 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