التسلسلات الهرمية المغلقة
جزء من قسم البرمجة كائنية التوجه في رحلة Kotlin على Coddy. الدرس 34 من 57.
لدى الفئة أو الواجهة 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التحدي
سهليفهم الروبوت أوامر الواجهة المختومة Command: Move(steps) وTurn(left) وReport وInvalid(text)، وهي متوفرة. أكمل parse(line): ينتج عن move 3 القيمة Move(3)، وينتج عن turn left وturn right القيمة Turn، وينتج عن report القيمة Report، بينما ينتج عن أي شيء آخر، بما في ذلك أمر حركة دون رقم صالح، القيمة Invalid مع السطر. ثم أكمل Robot.execute(command) باستخدام when شامل ومن دون else. يبدأ الروبوت عند (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)}")
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1الفئات والكائنات
العمل مع الملفاتتعريف الفئاتالمنشئات الأساسيةكتل init والتحقق من الصحةالمنشئات الثانويةمراجعة - بطاقة المكتبة4الوراثة
الأصناف المفتوحة والأصناف الفرعيةتجاوز الأعضاءsuper والمنشئاتالأصناف المجرّدةمراجعة - دليل الموظفين7الفئات الخاصة
فئات البياناتفئات التعدادالتسلسلات الهرمية المغلقةequals، hashCode، toStringمراجعة - متتبّع الطلباتتدرّب بنفسك: Playground لـ Kotlin