Динамическая диспетчеризация
Часть раздела Объектно-ориентированное программирование путешествия по 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"
}Задание
Легко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())
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПервичные конструкторыБлоки init и валидацияВторичные конструкторыПовторение — читательский билет4Наследование
Открытые классы и подклассыПереопределение членовsuper и конструкторыАбстрактные классыПовторение — каталог сотрудников7Специальные классы
Классы данныхКлассы перечисленийЗапечатанные иерархииequals, hashCode, toStringПовторение — трекер заказов10Паттерны проектирования. Часть 2
Делегирование с помощью byПаттерн декораторПаттерн строительПаттерн состояние2Свойства и инкапсуляция
Пользовательские аксессорыМодификаторы видимостиПриватные сеттерыlazy и lateinitПовторение — банковский счёт5Интерфейсы
Объявление интерфейсовМетоды по умолчаниюМножественные интерфейсыИнтерфейсы как типыПовторение — способы оплаты8Обобщения
Обобщённые функцииОбобщённые классыОграничения типовВариативность с in и outПовторение — очередь с приоритетом11Проект: Управление библиотекой
Книги и читателиВыдача книг3Объекты и компаньоны
Объявления объектовОбъекты-компаньоныКонстанты с constОбъектные выраженияПовторение — билетная касса6Полиморфизм
Динамическая диспетчеризацияПроверка типов и умные приведения типовwhen с типамиФункции-расширенияПовторение — смотритель зоопарка9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Песочница Kotlin