التفويض باستخدام 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التحدي
سهلالواجهة 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" })
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1الفئات والكائنات
العمل مع الملفاتتعريف الفئاتالمنشئات الأساسيةكتل init والتحقق من الصحةالمنشئات الثانويةمراجعة - بطاقة المكتبة4الوراثة
الأصناف المفتوحة والأصناف الفرعيةتجاوز الأعضاءsuper والمنشئاتالأصناف المجرّدةمراجعة - دليل الموظفينتدرّب بنفسك: Playground لـ Kotlin