Menu
Coddy logo textTech

Versiegelte Hierarchien

Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 34 von 57.

Eine sealed-Klasse oder -Schnittstelle verfügt über eine abgeschlossene Menge direkter Unterklassen, die alle im selben Paket und Modul deklariert sind. Der Compiler kennt jede von ihnen, was sich für Ergebnisse mit einer festen Anzahl von Formen eignet:

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

Innerhalb von main:

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

Ausgabe:

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

Da die Menge abgeschlossen ist, ist ein when über einen versiegelten Typ ohne else vollständig. Wenn später jemand eine Unterklasse hinzufügt, lässt sich jedes solche when nicht mehr kompilieren, bis es den neuen Fall behandelt, sodass kein Fall vergessen wird:

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

Innerhalb von main:

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

Ausgabe:

ok: 7
error: timeout

Ein Fall ohne Daten ist ein data object: eine einzelne Instanz mit einer lesbaren toString(). Sein when-Zweig benötigt kein is, da es nur eine Instanz gibt. Die anderen Fälle können Datenklassen, Objekte oder gewöhnliche Klassen sein:

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

Innerhalb von main:

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

Ausgabe:

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

Enum oder versiegelte Hierarchie? Ein Enum ist eine feste Menge einzelner Instanzen, die alle dieselben Eigenschaften haben. Eine versiegelte Hierarchie ermöglicht es jedem Fall, eigene Daten zu tragen, und ein Fall wie Move kann viele Instanzen mit unterschiedlichen Werten haben:

enum class Light { RED, YELLOW, GREEN }          // gleiche Form, jeweils eine Instanz

sealed interface Payment                          // jeder Fall hat seine eigenen Daten
data class Card(val number: String) : Payment
data class Transfer(val iban: String, val reference: String) : Payment
data object Cash : Payment
challenge icon

Aufgabe

Einfach

Ein Roboter versteht die Befehle des versiegelten Interfaces Command: Move(steps), Turn(left), Report und Invalid(text), die bereitgestellt werden. Vervollständige parse(line): move 3 ergibt Move(3), turn left und turn right ergeben Turn, report ergibt Report, und alles andere, einschließlich einer Bewegung ohne gültige Zahl, ergibt Invalid mit der Zeile. Vervollständige anschließend Robot.execute(command) mit einem vollständigen when und ohne else. Der Roboter startet bei (0, 0) mit Blickrichtung N (Norden ist y + 1, Osten ist x + 1). Er gibt moved to (0, 3), facing W, at (0, 3) facing W oder ignored zurück.

Der bereitgestellte Code analysiert jede Zeile und gibt Move(steps=3) -> moved to (0, 3) aus.

Dein Code gehört in Robot.kt, Parser.kt, Command.kt, Move.kt, Turn.kt, Report.kt und Invalid.kt. Main.kt enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

fun main() {
    // Gelieferter Ein-/Ausgabecode: belasse ihn wie er ist
    val input = generateSequence(::readLine).toList()
    val robot = Robot()
    for (line in input) {
        val command = parse(line)
        println("$command -> ${robot.execute(command)}")
    }
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Kotlin-Playground