Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

التسلسلات الهرمية المغلقة

جزء من قسم البرمجة كائنية التوجه في رحلة 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
challenge icon

التحدي

سهل

يفهم الروبوت أوامر الواجهة المختومة 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)}")
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: Playground لـ Kotlin