Menu
CoddyTech

Hệ phân cấp sealed

Thuộc phần Lập trình hướng đối tượng trong hành trình Kotlin của Coddy. Bài học 34 trên 57.

Một lớp hoặc giao diện sealed có một tập hợp đóng gồm các lớp con trực tiếp, tất cả đều được khai báo trong cùng một package và module. Trình biên dịch biết tất cả các lớp con đó, phù hợp với những kết quả có số dạng cố định:

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

Bên trong main:

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

Đầu ra:

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

Vì tập hợp này khép kín, câu lệnh when dùng với kiểu sealed có đầy đủ các trường hợp mà không cần else. Nếu sau này ai đó thêm một lớp con, mọi câu lệnh when như vậy sẽ không biên dịch được cho đến khi xử lý trường hợp mới, nhờ đó không trường hợp nào bị bỏ sót:

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

Bên trong main:

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

Đầu ra:

ok: 7
error: timeout

Trường hợp không có dữ liệu là một data object: một instance duy nhất có toString() dễ đọc. Nhánh when của nó không cần is, vì chỉ có một instance. Các trường hợp khác có thể là lớp dữ liệu, object hoặc lớp thông thường:

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

Bên trong main:

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

Đầu ra:

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

Enum hay hệ phân cấp sealed? Enum là một tập hợp cố định gồm các đối tượng đơn lẻ có cùng thuộc tính. Hệ phân cấp sealed cho phép mỗi trường hợp chứa dữ liệu riêng, và một trường hợp như Move có thể có nhiều đối tượng với các giá trị khác nhau:

enum class Light { RED, YELLOW, GREEN }          // Cùng cấu trúc, mỗi trường hợp có một instance

sealed interface Payment                          // Mỗi trường hợp có dữ liệu riêng
data class Card(val number: String) : Payment
data class Transfer(val iban: String, val reference: String) : Payment
data object Cash : Payment
challenge icon

Thử thách

Dễ

Robot hiểu các lệnh của sealed interface Command: Move(steps), Turn(left), Report và Invalid(text), đã được cung cấp. Hoàn thiện parse(line): move 3 cho ra Move(3), turn left và turn right cho ra Turn, report cho ra Report, còn mọi trường hợp khác, bao gồm cả lệnh di chuyển không có số hợp lệ, cho ra Invalid kèm theo dòng lệnh. Sau đó, hoàn thiện Robot.execute(command) bằng một when bao quát mọi trường hợp và không có else. Robot bắt đầu ở vị trí (0, 0), quay mặt về hướng N (hướng bắc là y + 1, hướng đông là x + 1). Robot trả về moved to (0, 3), facing W, at (0, 3) facing W hoặc ignored.

Code được cung cấp phân tích cú pháp từng dòng và in ra Move(steps=3) -> moved to (0, 3).

Đặt code của bạn trong các tệp Robot.kt, Parser.kt, Command.kt, Move.kt, Turn.kt, Report.kt và Invalid.kt. Main.kt chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

fun main() {
    // Đoạn code đầu vào/đầu ra có sẵn: giữ nguyên
    val input = generateSequence(::readLine).toList()
    val robot = Robot()
    for (line in input) {
        val command = parse(line)
        println("$command -> ${robot.execute(command)}")
    }
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Playground Kotlin