Запечатанные иерархии
Часть раздела Объектно-ориентированное программирование путешествия по Kotlin на Coddy. Урок 34 из 57.
Класс или интерфейс sealed имеет закрытый набор прямых подклассов, объявленных в одном пакете и модуле. Компилятор знает каждый из них, поэтому такой тип подходит для результатов с фиксированным числом вариантов:
sealed interface Result
data class Success(val value: Int) : Result
data class Failure(val reason: String) : Result
fun parseAge(text: String): Result {
val n = text.toIntOrNull() ?: return Failure("not a number: $text")
return if (n in 0..150) Success(n) else Failure("out of range: $n")
}Внутри main:
println(parseAge("42"))
println(parseAge("abc"))
println(parseAge("200"))Вывод:
Success(value=42)
Failure(reason=not a number: abc)
Failure(reason=out of range: 200)Поскольку множество замкнуто, конструкция when для запечатанного типа является исчерпывающей без else. Если позже кто-то добавит подкласс, каждая такая конструкция when перестанет компилироваться, пока не будет обработан новый случай, поэтому ни один случай не будет забыт:
sealed interface Result
data class Success(val value: Int) : Result
data class Failure(val reason: String) : Result
fun describe(r: Result) = when (r) {
is Success -> "ok: ${r.value}"
is Failure -> "error: ${r.reason}"
}Внутри main:
println(describe(Success(7)))
println(describe(Failure("timeout")))Вывод:
ok: 7
error: timeoutВариант без данных — это data object: единственный экземпляр с читаемым toString(). Его ветви when не требуется is, поскольку существует только один экземпляр. Остальные варианты могут быть классами данных, объектами или обычными классами:
sealed interface Command
data object Quit : Command
data class Move(val steps: Int) : Command
data class Say(val text: String) : Command
fun parse(line: String): Command = when {
line == "quit" -> Quit
line.startsWith("move ") -> Move(line.drop(5).toInt())
else -> Say(line)
}
fun run(c: Command) = when (c) {
Quit -> "bye"
is Move -> "moving ${c.steps}"
is Say -> "saying ${c.text}"
}Внутри main:
for (line in listOf("move 3", "hello", "quit")) {
val c = parse(line)
println("$c: ${run(c)}")
}Вывод:
Move(steps=3): moving 3
Say(text=hello): saying hello
Quit: byeПеречисление или запечатанная иерархия? Перечисление — это фиксированный набор отдельных экземпляров, у которых одинаковые свойства. Запечатанная иерархия позволяет каждому варианту содержать собственные данные, а такой вариант, как Move, может иметь множество экземпляров с разными значениями:
enum class Light { RED, YELLOW, GREEN } // одинаковая форма, по одному экземпляру каждого
sealed interface Payment // каждый случай имеет свои данные
data class Card(val number: String) : Payment
data class Transfer(val iban: String, val reference: String) : Payment
data object Cash : PaymentЗадание
ЛегкоРобот понимает команды запечатанного интерфейса Command: Move(steps), Turn(left), Report и Invalid(text), которые предоставлены. Реализуйте parse(line): move 3 даёт Move(3), turn left и turn right дают Turn, report даёт Report, а всё остальное, включая перемещение без допустимого числа, даёт Invalid со строкой. Затем реализуйте Robot.execute(command) с исчерпывающим when и без else. Робот начинает в позиции (0, 0), направляясь в сторону N (север — это y + 1, восток — это x + 1). Он возвращает moved to (0, 3), facing W, at (0, 3) facing W или ignored.
Предоставленный код разбирает каждую строку и выводит Move(steps=3) -> moved to (0, 3).
Ваш код должен находиться в файлах Robot.kt, Parser.kt, Command.kt, Move.kt, Turn.kt, Report.kt и Invalid.kt. В Main.kt находится предоставленный код ввода/вывода, и его нельзя редактировать.
Попробуйте сами
fun main() {
// Предоставленный код ввода/вывода: оставьте его как есть
val input = generateSequence(::readLine).toList()
val robot = Robot()
for (line in input) {
val command = parse(line)
println("$command -> ${robot.execute(command)}")
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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