Menu
Coddy logo textTech

האצלה באמצעות 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

מאפיינים מואצלים מעבירים את פעולות הקריאה והכתיבה של מאפיין לאובייקט אחר, שוב באמצעות 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בחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: Kotlin אונליין