Menu
Coddy logo textTech

Délégation avec by

Fait partie de la section Programmation orientée objet du Journey Kotlin de Coddy. Leçon 46 sur 57.

Délégation signifie qu’un objet confie une tâche à un autre objet au lieu d’en hériter. Kotlin la prend directement en charge : class Polite(private val inner: Greeter) : Greeter by inner implémente Greeter en transmettant chaque appel à inner, et la classe ne redéfinit que ce qu’elle modifie :

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

Dans main :

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

Sortie :

Hello, Ada, pleased to meet you
Bye, Ada

La délégation est une alternative à l’héritage. Elle fonctionne avec des interfaces dont les implémentations sont finales, et le délégué peut être n’importe quel objet choisi pendant l’exécution du programme. Cet ensemble compte chaque add et transmet tout le reste à un MutableSet normal :

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

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

Sortie :

2 tags after 3 adds, contains jvm: true

Les propriétés déléguées confient l’accesseur et le mutateur d’une propriété à un autre objet, toujours avec by. lazy du chapitre 2 est un tel délégué, et une map peut en être un autre : chaque propriété lit l’entrée portant son propre nom :

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

Dans main :

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

Sortie :

Ada is 36

Vous pouvez écrire votre propre délégué de propriété : une classe avec un operator fun getValue, ainsi que setValue pour une var. Kotlin les appelle à chaque lecture et écriture de la propriété. Celui-ci supprime les espaces superflus de chaque texte assigné et compte les modifications :

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
}

Dans main :

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

Sortie :

[Ada L.] after 2 changes
challenge icon

Défi

Facile

L’interface Storage (avec put, get et keys) et son implémentation MapStorage sont fournies. Écrivez deux décorateurs avec délégation de classe, chacun ne redéfinissant que put : LoggingStorage(inner) enregistre put a=1 dans sa liste log en lecture seule et transmet l’appel, tandis que LimitedStorage(inner, max) refuse (renvoie false) d’ajouter une nouvelle clé dès que max clés existent, alors que la mise à jour des clés existantes reste autorisée. Tout le reste est transmis avec by.

Le code fourni enveloppe un MapStorage dans un LoggingStorage et, lorsque la première ligne contient une limite positive, dans un LimitedStorage. Il exécute les commandes put a 1, get a et keys, en affichant ok, refused, la valeur ou missing, ainsi que la liste des clés, puis finalement log: put a=1, ....

Votre code doit se trouver dans LoggingStorage.kt, LimitedStorage.kt, Storage.kt et MapStorage.kt. Main.kt contient le code d’entrée/sortie fourni et ne peut pas être modifié.

Essayez vous-même

fun main() {
    // Code d'entrée/sortie fourni : le garder tel quel
    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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Playground Kotlin