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, AdaLa 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: trueLas 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 36Puedes 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 changesDesafío
FácilLa 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" })
}
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
1Clases y objetos
Trabajar con archivosDefinir clasesConstructores primariosBloques init y validaciónConstructores secundariosRepaso - Tarjeta de biblioteca4Herencia
Clases abiertas y subclasesSobrescritura de miembrossuper y constructoresClases abstractasRepaso: directorio del personal2Propiedades y encapsulación
Accesores personalizadosModificadores de visibilidadSetters privadoslazy y lateinitRepaso: cuenta bancaria5Interfaces
Declaración de interfacesMétodos predeterminadosInterfaces múltiplesInterfaces como tiposRepaso: métodos de pago8Genéricos
Funciones genéricasClases genéricasRestricciones de tiposVariancia con in y outRepaso - Cola de prioridad11Proyecto: Gestión de biblioteca
Libros y miembrosPréstamo de librosPractica por tu cuenta: Playground de Kotlin