Menu
Coddy logo textTech

Запечатанные иерархии

Часть раздела Объектно-ориентированное программирование путешествия по 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
challenge icon

Задание

Легко

Робот понимает команды запечатанного интерфейса 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)}")
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Песочница Kotlin