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

الأصناف المجرّدة

جزء من قسم البرمجة كائنية التوجه في رحلة Kotlin على Coddy. الدرس 20 من 57.

الفئة abstract هي فئة فائقة لا يمكن إنشاء مثيل لها بمفردها. ويمكنها التصريح بأعضاء abstract دون جسم، ويجب على كل فئة فرعية ملموسة تنفيذها:

abstract class Shape(val name: String) {
    abstract fun area(): Double

    fun describe() = "$name with area ${area()}"
}
class Circle(val r: Double) : Shape("circle") {
    override fun area() = 3.14 * r * r
}
class Rect(val w: Double, val h: Double) : Shape("rect") {
    override fun area() = w * h
}

داخل main:

val shapes: List<Shape> = listOf(Circle(1.0), Rect(2.0, 3.0))
for (s in shapes) println(s.describe())

الناتج:

circle with area 3.14
rect with area 6.0

تكون الفئة المجرّدة مفتوحة تلقائيًا من دون الكلمة المفتاحية، كما أن أعضائها المجرّدة مفتوحة أيضًا، لذلك لا تكتب الفئات الفرعية سوى override. لا يتم تجميع إنشاء الفئة المجرّدة نفسها، ويجب أن تكون الفئة الفرعية التي تترك عضوًا مجرّدًا دون تطبيق مجرّدةً هي الأخرى. استخدم فئة مجرّدة عندما لا يكون للفئة الفائقة سلوك معقول خاص بها لبعض الأعضاء:

// val s = Shape("blob")                       // خطأ: لا يمكن إنشاء مثيل لفئة مجردة
// class Blob : Shape("blob")                  // خطأ: Blob لا يُنفِّذ area()
abstract class Polygon(name: String) : Shape(name)   // حسنًا: لا يزال مجردًا

يمكن أن يحتوي الصنف المجرّد على مُنشئ وحالة وطرائق عادية، ترثها الأصناف الفرعية. ومن الاستخدامات الشائعة إجراء ثابت تتغير خطواته، ويُسمّى طريقة القالب: تنفّذ طريقة الصنف الفائق الخطوات، بينما تملأ الأصناف الفرعية الطرائق المجرّدة:

abstract class Report(val title: String) {
    fun render() = "== $title ==\n${body()}\n(${footer()})"

    abstract fun body(): String
    open fun footer() = "end"
}
class Sales(val total: Int) : Report("Sales") {
    override fun body() = "total: $total"
}
class Stock(val items: Int) : Report("Stock") {
    override fun body() = "items: $items"
    override fun footer() = "counted today"
}

داخل main:

println(Sales(420).render())
println(Stock(17).render())

الناتج:

== Sales ==
total: 420
(end)
== Stock ==
items: 17
(counted today)

تعمل الخصائص المجرّدة بالطريقة نفسها. يوفّر الصنف الفرعي القيمة باعتبارها خاصية مُمرَّرة إلى المُنشئ، أو خاصية مخزّنة، أو دالة getter:

abstract class Worker(val name: String) {
    abstract val monthly: Int
    fun yearly() = monthly * 12
}
class Salaried(name: String, override val monthly: Int) : Worker(name)
class Hourly(name: String, val hours: Int, val rate: Int) : Worker(name) {
    override val monthly: Int
        get() = hours * rate
}

داخل main:

val staff = listOf(Salaried("Ada", 4000), Hourly("Bo", 80, 25))
for (w in staff) println("${w.name}: ${w.yearly()}")

الناتج:

Ada: 48000
Bo: 24000
challenge icon

التحدي

سهل

Discount هو صنف مجرد. يعلن عن الطريقة المجردة amount(price) وينفّذ apply(price)، التي تُرجع السعر مطروحًا منه amount(price)، ولكن لا تُرجع أبدًا قيمة أقل من 0. أكمل الأصناف الفرعية: يأخذ PercentOff(percent) تلك النسبة المئوية من السعر (مع التقريب إلى الأسفل)، ويأخذ FixedOff(value) قيمة ثابتة، بينما يأخذ SpendMore(threshold, off) القيمة off فقط عندما يكون السعر أكبر من أو يساوي threshold.

يقرأ الكود المُعطى السعر، ثم يطبّق الخصم p,10 أو f,300 أو s,500,50، ويطبّق كل خصم على السعر ويطبع 1000 -> 900.

يجب أن تضع كودك في Discount.kt وPercentOff.kt وFixedOff.kt وSpendMore.kt. يحتوي Main.kt على كود الإدخال/الإخراج المُعطى، ولا يمكن تعديله.

جرّب بنفسك

fun main() {
    // كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
    val input = generateSequence(::readLine).toList()
    val price = input[0].toInt()
    for (line in input.drop(1)) {
        val p = line.split(",")
        val discount: Discount = when (p[0]) {
            "p" -> PercentOff(p[1].toInt())
            "f" -> FixedOff(p[1].toInt())
            else -> SpendMore(p[1].toInt(), p[2].toInt())
        }
        println("$price -> ${discount.apply(price)}")
    }
}
quiz iconاختبر نفسك

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

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

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