Menu
Coddy logo textTech

봉인된 계층 구조

Coddy Kotlin 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 34번째.

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를 생성합니다. 그런 다음 완전한 when문을 사용하고 else를 사용하지 않도록 Robot.execute(command)를 완성하세요. 로봇은 (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 플레이그라운드