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

الكائنات المرافقة

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

تنتمي بعض الأعضاء إلى الفئة ككل بدلاً من مثيل واحد. يضعها Kotlin داخل companion object في الفئة، ويمكنك استدعاؤها من خلال اسم الفئة:

class Temperature(val celsius: Double) {
    companion object {
        fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
    }
}

داخل main:

val boiling = Temperature.fromFahrenheit(212.0)
println(boiling.celsius)

الناتج:

100.0

fromFahrenheit هي دالة مصنع: دالة تُنشئ مثيلات. وعلى خلاف المُنشئ، لها اسم وصفي، ويمكنها إرجاع null بدلًا من كائن. ومع وجود private constructor، تكون دالة المصنع هي الطريقة الوحيدة للدخول، لذا يمكنها التحقق من الإدخال وتنظيفه أولًا:

class Username private constructor(val text: String) {
    companion object {
        fun of(raw: String): Username? {
            val clean = raw.trim().lowercase()
            return if (clean.length in 3..12) Username(clean) else null
        }
    }
}

داخل main:

println(Username.of("  Ada_L ")?.text)
println(Username.of("x")?.text)
// Username("x")   // خطأ: المُنشئ خاص

الناتج:

ada_l
null

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

class Ticket(val holder: String) {
    val number = nextNumber++

    companion object {
        private var nextNumber = 1
        val issued: Int
            get() = nextNumber - 1
    }
}

داخل main:

val a = Ticket("Ada")
val b = Ticket("Bo")
println("${a.holder} #${a.number}, ${b.holder} #${b.number}")
println("issued: ${Ticket.issued}")

الناتج:

Ada #1, Bo #2
issued: 2

لا تحتوي Kotlin على الكلمة المفتاحية static. تؤدي أعضاء الكائن المرافق هذا الدور: نسخة واحدة للفئة بأكملها، ويمكن الوصول إليها عبر ClassName.member. وكأي كائن، يُنشأ الكائن المرافق مرة واحدة، عند استخدام الفئة لأول مرة. ضع فيه ما لا يعتمد على مثيل واحد، مثل دوال الإنشاء والعدادات المشتركة؛ أما كل ما يقرأ بيانات مثيل، فيبقى عضوًا عاديًا.

class User private constructor(val name: String) {
    fun greet() = "hi $name"             // يحتاج إلى مثيل: user.greet()

    companion object {
        fun guest() = User("guest")      // ينتمي إلى الصنف: User.guest()
    }
}
challenge icon

التحدي

سهل

أكمل الصنف Coupon. مُنشئه خاص، لذا تُنشأ القسائم بواسطة المصنع Coupon.create(code, percent) في الكائن المرافق. ويُرجع null ما لم يكن الرمز مكوّنًا من 4 إلى 10 محارف من الأحرف الكبيرة والأرقام، وكانت النسبة المئوية بين 1 و90. كما يحصي الكائن المرافق القسائم التي أنشأها في created، ويمكن للكود الخارجي قراءة هذه القيمة ولكن لا يمكنه تغييرها. تُرجع apply(price) السعر مطروحًا منه الخصم، مع التقريب إلى الأسفل (price * (100 - percent) / 100).

يقرأ الكود المرفق الأسطر SAVE10 10 200 (الرمز، النسبة المئوية، السعر). ويطبع SAVE10: 180 أو invalid coupon SAVE10، وأخيرًا created: متبوعًا بالعدد.

ضع الكود في Coupon.kt. يحتوي Main.kt على كود الإدخال/الإخراج المرفق، ولا يمكن تعديله.

جرّب بنفسك

fun main() {
    // كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
    val input = generateSequence(::readLine).toList()
    for (line in input) {
        val (code, percent, price) = line.split(" ")
        val coupon = Coupon.create(code, percent.toInt())
        if (coupon == null) println("invalid coupon $code") else println("${coupon.code}: ${coupon.apply(price.toInt())}")
    }
    println("created: ${Coupon.created}")
}
quiz iconاختبر نفسك

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

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

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