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: timeoutUn 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 : PaymentDesafío
FácilUn 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)}")
}
}
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
1Clases y objetos
Trabajar con archivosDefinir clasesConstructores primariosBloques init y validaciónConstructores secundariosRepaso - Tarjeta de biblioteca4Herencia
Clases abiertas y subclasesSobrescritura de miembrossuper y constructoresClases abstractasRepaso: directorio del personal7Clases especiales
Clases de datosClases enumJerarquías selladasequals, hashCode, toStringRepaso - Rastreador de pedidos2Propiedades y encapsulación
Accesores personalizadosModificadores de visibilidadSetters privadoslazy y lateinitRepaso: cuenta bancaria5Interfaces
Declaración de interfacesMétodos predeterminadosInterfaces múltiplesInterfaces como tiposRepaso: métodos de pago8Genéricos
Funciones genéricasClases genéricasRestricciones de tiposVariancia con in y outRepaso - Cola de prioridad11Proyecto: Gestión de biblioteca
Libros y miembrosPréstamo de librosPractica por tu cuenta: Playground de Kotlin