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: timeoutTrườ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: byeEnum 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 : PaymentThử 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)}")
}
}
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
1Lớp và đối tượng
Làm việc với tệpĐịnh nghĩa lớpHàm khởi tạo chínhKhối init và kiểm tra tính hợp lệHàm khởi tạo phụÔn tập: thẻ thư viện4Kế thừa
Lớp mở và lớp conGhi đè thành viênsuper và hàm khởi tạoLớp trừu tượngÔn tập: Danh bạ nhân viên7Các lớp đặc biệt
Lớp dữ liệuLớp enumHệ phân cấp sealedequals, hashCode, toStringÔn tập: Theo dõi đơn hàng2Thuộc tính và tính đóng gói
Trình truy cập tùy chỉnhBộ sửa đổi phạm vi truy cậpSetter riêng tưlazy và lateinitÔn tập: tài khoản ngân hàng5Giao diện
Khai báo giao diệnPhương thức mặc địnhNhiều giao diệnGiao diện như kiểu dữ liệuÔn tập: Phương thức thanh toánTự luyện tập: Playground Kotlin