Menu
Coddy logo textTech

Delegation mit by

Teil des Abschnitts Objektorientierte Programmierung der Kotlin-Journey von Coddy. Lektion 46 von 57.

Delegation bedeutet, dass ein Objekt Arbeit an ein anderes Objekt übergibt, anstatt sie zu erben. Kotlin unterstützt dies direkt: class Polite(private val inner: Greeter) : Greeter by inner implementiert Greeter, indem es jeden Aufruf an inner weiterleitet, und die Klasse überschreibt nur das, was sie ändert:

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"
}

Innerhalb von main:

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

Ausgabe:

Hello, Ada, pleased to meet you
Bye, Ada

Delegation ist eine Alternative zur Vererbung. Sie funktioniert mit Schnittstellen, deren Implementierungen final sind, und der Delegat kann jedes Objekt sein, das während der Programmausführung ausgewählt wird. Dieses Set zählt jedes add und leitet alles andere an ein normales MutableSet weiter:

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)
    }
}

Innerhalb von 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}")

Ausgabe:

2 tags after 3 adds, contains jvm: true

Delegierte Eigenschaften übergeben den Getter und Setter einer Eigenschaft an ein anderes Objekt, wiederum mit by. lazy aus Kapitel 2 ist ein solcher Delegat, und eine Map kann ein weiterer sein: Jede Eigenschaft liest den Eintrag mit ihrem eigenen Namen:

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

Innerhalb von main:

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

Ausgabe:

Ada is 36

Du kannst deinen eigenen Property-Delegaten schreiben: eine Klasse mit einem operator fun getValue sowie setValue für ein var. Kotlin ruft sie bei jedem Lesen und Schreiben der Property auf. Dieser entfernt bei jedem zugewiesenen Text die Leerzeichen und zählt die Änderungen:

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
}

Innerhalb von main:

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

Ausgabe:

[Ada L.] after 2 changes
challenge icon

Aufgabe

Einfach

Die Schnittstelle Storage (mit put, get und keys) und ihre Implementierung MapStorage werden bereitgestellt. Schreibe zwei Decorators mit Klassendelegation, die jeweils nur put überschreiben: LoggingStorage(inner) protokolliert put a=1 in seiner schreibgeschützten log-Liste und leitet den Aufruf weiter, und LimitedStorage(inner, max) verweigert (gibt false zurück) das Hinzufügen eines neuen Schlüssels, sobald max Schlüssel vorhanden sind, während Aktualisierungen bestehender Schlüssel weiterhin erlaubt sind. Alles andere wird mit by weitergeleitet.

Der bereitgestellte Code verpackt einen MapStorage in einen LoggingStorage und bei einem positiven Limit in der ersten Zeile zusätzlich in einen LimitedStorage. Er führt die Befehle put a 1, get a und keys aus und gibt ok, refused, den Wert oder missing sowie die Schlüsselliste aus und schließlich log: put a=1, ....

Dein Code gehört in LoggingStorage.kt, LimitedStorage.kt, Storage.kt und MapStorage.kt. Main.kt enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

fun main() {
    // Bereitgestellter Ein-/Ausgabecode: belasse ihn wie er ist
    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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Kotlin-Playground