Уведомление Urgent всё равно отрисовывается как обычное — найдите и исправьте ошибку
Оба вида уведомлений отрисовываются из одной ленты. Версии render() и badge для Urgent не используются никогда: каждая строка выводится как обычное уведомление.
Ограничения: лента обязана остаться List<Notification>, цикл — прежним, а элемент Urgent обязан напечатать alarm URGENT: prod down.
open class Notification(val text: String)
class Urgent(text: String) : Notification(text)
fun Notification.render(): String = "note: $text"
fun Urgent.render(): String = "URGENT: $text"
val Notification.badge: String get() = "info"
val Urgent.badge: String get() = "alarm"
fun main() {
val feed: List<Notification> = listOf(Notification("build ok"), Urgent("prod down"))
feed.forEach { println("${it.badge} ${it.render()}") }
// info note: build ok
// info note: prod down <-- ожидалось: alarm URGENT: prod down
}
Найдите и исправьте ошибку.
Расширение разрешается по объявленному статическому типу, а не по типу времени выполнения: оно компилируется в статическую функцию и ничего не переопределяет, поэтому при it: Notification берутся версии для Notification. Нужны open-члены и override.
- ✗Ждать от расширения виртуальности и переопределения версии для базового класса
- ✗Читать ресивер как тип времени выполнения значения, а не как объявленный тип
- ✗Пометить класс как
openи ждать, что это изменит разрешение расширения
- →Что делать, если
Notificationпришёл из библиотеки, которую нельзя менять? - →Какая версия победит, если у функции-члена и расширения одна сигнатура?
Почему выводится базовая версия
Расширения статически диспетчеризуются. Компилятор превращает fun Notification.render() в обычную статическую функцию, принимающую ресивер первым параметром, — в класс Notification ничего не добавляется, виртуальной таблицы у неё нет. Поэтому расширение выбирается по объявленному типу выражения, а не по типу объекта в рантайме.
Внутри forEach над List<Notification> параметр it объявлен как Notification. Значит для каждого элемента — включая Urgent — берутся Notification.render() и Notification.badge. Расширение для Urgent не переопределяет ничего: оно просто никогда не выбирается на этом месте вызова.
Исправление: сделать поведение членами и переопределить
open class Notification(val text: String) {
open val badge: String get() = "info"
open fun render(): String = "note: $text"
}
class Urgent(text: String) : Notification(text) {
override val badge: String get() = "alarm"
override fun render(): String = "URGENT: $text"
}
fun main() {
val feed: List<Notification> = listOf(Notification("build ok"), Urgent("prod down"))
feed.forEach { println("${it.badge} ${it.render()}") }
// info note: build ok
// alarm URGENT: prod down
}
Члены класса диспетчеризуются виртуально, поэтому override в Urgent побеждает по рантайм-типу.
Если бы Notification пришёл из библиотеки и добавить членов было нельзя, полиморфизм пришлось бы восстанавливать явно — например, when (it) { is Urgent -> it.render() else -> it.render() }, — но расширение виртуальным не станет никогда.