Menu
Coddy logo textTech

Delegazione con by

Fa parte della sezione Programmazione orientata agli oggetti del percorso Kotlin di Coddy. Lezione 46 di 57.

La delega significa che un oggetto affida il lavoro a un altro oggetto invece di ereditarlo. Kotlin la supporta direttamente: class Polite(private val inner: Greeter) : Greeter by inner implementa Greeter inoltrando ogni chiamata a inner, e la classe sovrascrive solo ciò che modifica:

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

All'interno di main:

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

Output:

Hello, Ada, pleased to meet you
Bye, Ada

La delega è un’alternativa all’ereditarietà. Funziona con interfacce le cui implementazioni sono finali e il delegato può essere qualsiasi oggetto scelto durante l’esecuzione del programma. Questo insieme conta ogni add e inoltra tutto il resto a un normale 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)
    }
}

All’interno di 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}")

Output:

2 tags after 3 adds, contains jvm: true

Le proprietà delegate affidano il getter e il setter di una proprietà a un altro oggetto, sempre con by. lazy del capitolo 2 è un esempio di delega, e anche una mappa può esserlo: ogni proprietà legge la voce con il proprio nome:

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

Dentro main:

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

Output:

Ada is 36

Puoi scrivere un delegato di proprietà personalizzato: una classe con un operator fun getValue e, per una var, anche setValue. Kotlin li richiama a ogni lettura e scrittura della proprietà. Questo delegato rimuove gli spazi superflui da ogni testo assegnato e conta le modifiche:

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
}

All'interno di main:

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

Output:

[Ada L.] after 2 changes
challenge icon

Sfida

Facile

L'interfaccia Storage (con put, get e keys) e la sua implementazione MapStorage sono fornite. Scrivi due decoratori con delega di classe, ciascuno dei quali sovrascrive solo put: LoggingStorage(inner) registra put a=1 nella sua lista log di sola lettura e inoltra la chiamata, mentre LimitedStorage(inner, max) rifiuta (restituisce false) di aggiungere una nuova chiave quando esistono già max chiavi, consentendo comunque gli aggiornamenti delle chiavi esistenti. Tutto il resto viene inoltrato con by.

Il codice fornito racchiude un MapStorage in un LoggingStorage e, quando la prima riga è un limite positivo, lo racchiude in un LimitedStorage. Esegue i comandi put a 1, get a e keys, stampando ok, refused, il valore oppure missing, e l'elenco delle chiavi, infine log: put a=1, ....

Il tuo codice va inserito in LoggingStorage.kt, LimitedStorage.kt, Storage.kt e MapStorage.kt. Main.kt contiene il codice di input/output fornito e non può essere modificato.

Provalo tu

fun main() {
    // Codice di input/output fornito: lascialo così com'è
    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 iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Playground Kotlin