Menu
Coddy logo textTech

Delegación con by

Parte de la sección Programación orientada a objetos del Journey de Kotlin de Coddy. Lección 46 de 57.

Delegación significa que un objeto delega el trabajo a otro objeto en lugar de heredarlo. Kotlin lo admite directamente: class Polite(private val inner: Greeter) : Greeter by inner implementa Greeter reenviando cada llamada a inner, y la clase solo sobrescribe lo que cambia:

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

Dentro de main:

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

Salida:

Hello, Ada, pleased to meet you
Bye, Ada

La delegación es una alternativa a la herencia. Funciona con interfaces cuyas implementaciones son finales, y el delegado puede ser cualquier objeto elegido mientras se ejecuta el programa. Este conjunto cuenta cada add y reenvía todo lo demás a 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)
    }
}

Dentro de 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}")

Salida:

2 tags after 3 adds, contains jvm: true

Las propiedades delegadas entregan el getter y el setter de una propiedad a otro objeto, de nuevo con by. lazy del capítulo 2 es uno de esos delegados, y un mapa puede ser otro: cada propiedad lee la entrada con su propio nombre:

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

Dentro de main:

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

Salida:

Ada is 36

Puedes escribir tu propio delegado de propiedades: una clase con un operator fun getValue, además de setValue para un var. Kotlin los llama cada vez que se lee o escribe la propiedad. Este recorta cada texto asignado y cuenta los cambios:

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
}

Dentro de main:

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

Salida:

[Ada L.] after 2 changes
challenge icon

Desafío

Fácil

La interfaz Storage (con put, get y keys) y su implementación MapStorage se proporcionan. Escribe dos decoradores con delegación de clases, cada uno sobrescribiendo únicamente put: LoggingStorage(inner) registra put a=1 en su lista de solo lectura log y reenvía la llamada, y LimitedStorage(inner, max) se niega (devuelve false) a añadir una clave nueva cuando ya existen max claves, mientras que las actualizaciones de claves existentes siguen estando permitidas. Todo lo demás se reenvía con by.

El código proporcionado envuelve un MapStorage en un LoggingStorage y, cuando la primera línea es un límite positivo, lo envuelve en un LimitedStorage. Ejecuta los comandos put a 1, get a y keys, mostrando ok, refused, el valor o missing, y la lista de claves, y finalmente log: put a=1, ....

Tu código debe estar en LoggingStorage.kt, LimitedStorage.kt, Storage.kt y MapStorage.kt. Main.kt contiene el código de entrada/salida proporcionado y no se puede editar.

Pruébalo tú mismo

fun main() {
    // Código de entrada/salida suministrado: déjalo tal como está
    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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Playground de Kotlin