Menu
Coddy logo textTech

Jerarquías selladas

Parte de la sección Programación orientada a objetos del Journey de Kotlin de Coddy. Lección 34 de 57.

Una clase o interfaz sealed tiene un conjunto cerrado de subclases directas, todas declaradas en el mismo paquete y módulo. El compilador conoce cada una de ellas, lo que resulta adecuado para resultados que tienen un número fijo de formas:

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")
}

Dentro de main:

println(parseAge("42"))
println(parseAge("abc"))
println(parseAge("200"))

Salida:

Success(value=42)
Failure(reason=not a number: abc)
Failure(reason=out of range: 200)

Como el conjunto está cerrado, un when sobre un tipo sellado es exhaustivo sin else. Si alguien añade una subclase más adelante, cada when de este tipo deja de compilar hasta que gestione el nuevo caso, por lo que no se olvida ningún caso:

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}"
}

Dentro de main:

println(describe(Success(7)))
println(describe(Failure("timeout")))

Salida:

ok: 7
error: timeout

Un caso sin datos es un data object: una única instancia con un toString() legible. Su rama when no necesita is, porque solo hay una instancia. Los otros casos pueden ser clases de datos, objetos o clases ordinarias:

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}"
}

Dentro de main:

for (line in listOf("move 3", "hello", "quit")) {
    val c = parse(line)
    println("$c: ${run(c)}")
}

Salida:

Move(steps=3): moving 3
Say(text=hello): saying hello
Quit: bye

¿Enum o jerarquía sellada? Un enum es un conjunto fijo de instancias únicas que tienen todas las mismas propiedades. Una jerarquía sellada permite que cada caso incluya sus propios datos, y un caso como Move puede tener muchas instancias con valores diferentes:

enum class Light { RED, YELLOW, GREEN }          // misma forma, una instancia de cada uno

sealed interface Payment                          // cada caso tiene sus propios datos
data class Card(val number: String) : Payment
data class Transfer(val iban: String, val reference: String) : Payment
data object Cash : Payment
challenge icon

Desafío

Fácil

Un robot entiende los comandos de la interfaz sellada Command: Move(steps), Turn(left), Report e Invalid(text), que se proporcionan. Completa parse(line): move 3 produce Move(3), turn left y turn right producen Turn, report produce Report, y cualquier otra cosa, incluido un movimiento sin un número válido, produce Invalid con la línea. Después, completa Robot.execute(command) con un when exhaustivo y sin else. El robot comienza en (0, 0) orientado hacia N (el norte es y + 1 y el este es x + 1). Devuelve moved to (0, 3), facing W, at (0, 3) facing W o ignored.

El código proporcionado analiza cada línea e imprime Move(steps=3) -> moved to (0, 3).

Tu código va en Robot.kt, Parser.kt, Command.kt, Move.kt, Turn.kt, Report.kt e Invalid.kt. Main.kt contiene el código proporcionado de entrada/salida y no se puede editar.

Pruébalo tú mismo

fun main() {
    // Código de entrada/salida suministrado: déjalo tal como está
    val input = generateSequence(::readLine).toList()
    val robot = Robot()
    for (line in input) {
        val command = parse(line)
        println("$command -> ${robot.execute(command)}")
    }
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Playground de Kotlin