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

التفويض باستخدام by

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

التفويض يعني أن الكائن يسلّم العمل إلى كائن آخر بدلاً من وراثته. يدعمه Kotlin مباشرةً: class Polite(private val inner: Greeter) : Greeter by inner يطبّق Greeter عبر إعادة توجيه كل استدعاء إلى inner، ولا يتجاوز الصنف إلا ما يغيّره:

interface Greeter {
    fun hello(name: String): String
    fun bye(name: String): String
}
class English : Greeter {
    override fun hello(name: String) = "Hello, $name"
    override fun bye(name: String) = "Bye, $name"
}
class Polite(private val inner: Greeter) : Greeter by inner {
    override fun hello(name: String) = inner.hello(name) + ", pleased to meet you"
}

داخل main:

val g = Polite(English())
println(g.hello("Ada"))
println(g.bye("Ada"))

الناتج:

Hello, Ada, pleased to meet you
Bye, Ada

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

class CountingSet<T>(private val inner: MutableSet<T> = mutableSetOf()) : MutableSet<T> by inner {
    var attempts = 0
        private set

    override fun add(element: T): Boolean {
        attempts++
        return inner.add(element)
    }
}

داخل main:

val tags = CountingSet<String>()
tags.add("kotlin")
tags.add("jvm")
tags.add("kotlin")
println("${tags.size} tags after ${tags.attempts} adds, contains jvm: ${"jvm" in tags}")

الناتج:

2 tags after 3 adds, contains jvm: true

الخصائص المفوَّضة تسلّم دالة getter ودالة setter الخاصة بالخاصية إلى كائن آخر، مجددًا باستخدام by. تُعدّ lazy من الفصل 2 أحد هذه المفوَّضين، ويمكن أن تكون الخريطة مفوَّضًا آخر: إذ تقرأ كل خاصية المدخلة التي تحمل اسمها:

class User(values: Map<String, Any?>) {
    val name: String by values
    val age: Int by values
}

داخل main:

val u = User(mapOf("name" to "Ada", "age" to 36))
println("${u.name} is ${u.age}")

الناتج:

Ada is 36

يمكنك كتابة مفوَّض خاص بك للخاصية: فئة تحتوي على operator fun getValue، بالإضافة إلى setValue من أجل var. تستدعيها Kotlin عند كل قراءة وكتابة للخاصية. هذا المفوَّض يزيل المسافات من كل نص مُسنَد ويحصي التغييرات:

import kotlin.reflect.KProperty

class Trimmed {
    private var value = ""
    var changes = 0

    operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        value = newValue.trim()
        changes++
    }
}

class Form {
    val nameField = Trimmed()
    var name: String by nameField
}

داخل main:

val form = Form()
form.name = "  Ada  "
form.name = "Ada L. "
println("[${form.name}] after ${form.nameField.changes} changes")

الناتج:

[Ada L.] after 2 changes
challenge icon

التحدي

سهل

الواجهة Storage (مع put وget وkeys) وتطبيقها MapStorage مُقدَّمان. اكتب مُزخرفَين باستخدام تفويض الأصناف، بحيث يتجاوز كل منهما put فقط: يسجّل LoggingStorage(inner) القيمة put a=1 في قائمته log للقراءة فقط ويمرّر الاستدعاء، بينما يرفض LimitedStorage(inner, max) (يعيد false) إضافة مفتاح جديد عند وجود max من المفاتيح، مع السماح بتحديث المفاتيح الموجودة. وكل شيء آخر يُمرَّر باستخدام by.

تغلّف الشيفرة المقدَّمة كائن MapStorage داخل LoggingStorage، وعندما يكون السطر الأول حدًّا موجبًا، تغلّفه أيضًا داخل LimitedStorage. وهي تنفّذ الأوامر put a 1 وget a وkeys، وتطبع ok أو refused أو القيمة أو missing، وقائمة المفاتيح، ثم تطبع في النهاية log: put a=1, ....

ضع شيفرتك في LoggingStorage.kt وLimitedStorage.kt وStorage.kt وMapStorage.kt. يحتوي Main.kt على شيفرة الإدخال/الإخراج المقدَّمة، ولا يمكن تعديله.

جرّب بنفسك

fun main() {
    // كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
    val input = generateSequence(::readLine).toList()
    val base = MapStorage()
    val logging = LoggingStorage(base)
    val limit = input[0].toInt()
    val storage: Storage = if (limit > 0) LimitedStorage(logging, limit) else logging
    for (cmd in input.drop(1)) {
        val p = cmd.split(" ")
        when (p[0]) {
            "put" -> println(if (storage.put(p[1], p[2])) "ok" else "refused")
            "get" -> println(storage.get(p[1]) ?: "missing")
            else -> println(storage.keys())
        }
    }
    println("log: " + logging.log.joinToString(", ").ifEmpty { "empty" })
}
quiz iconاختبر نفسك

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

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

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