Menu
Coddy logo textTech

Delegação com by

Parte da seção Programação Orientada a Objetos do Journey de Kotlin da Coddy. Lição 46 de 57.

Delegação significa que um objeto delega o trabalho a outro objeto em vez de herdá-lo. Kotlin oferece suporte direto a isso: class Polite(private val inner: Greeter) : Greeter by inner implementa Greeter encaminhando cada chamada para inner, e a classe substitui apenas o que altera:

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

Saída:

Hello, Ada, pleased to meet you
Bye, Ada

Delegação é uma alternativa à herança. Ela funciona com interfaces cujas implementações são finais, e o delegado pode ser qualquer objeto escolhido enquanto o programa é executado. Este conjunto conta cada add e encaminha todo o restante para um MutableSet comum:

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

Saída:

2 tags after 3 adds, contains jvm: true

Propriedades delegadas transferem o getter e o setter de uma propriedade para outro objeto, novamente com by. lazy do capítulo 2 é um desses delegados, e um mapa pode ser outro: cada propriedade lê a entrada com seu próprio nome:

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

Saída:

Ada is 36

Você pode escrever seu próprio delegado de propriedade: uma classe com um operator fun getValue, além de setValue para uma var. O Kotlin os chama a cada leitura e escrita da propriedade. Este remove os espaços em branco de todo texto atribuído e conta as alterações:

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

Saída:

[Ada L.] after 2 changes
challenge icon

Desafio

Fácil

A interface Storage (com put, get e keys) e sua implementação MapStorage são fornecidas. Escreva dois decoradores com delegação por classe, cada um sobrescrevendo apenas put: LoggingStorage(inner) registra put a=1 em sua lista somente para leitura log e encaminha a chamada, e LimitedStorage(inner, max) recusa (retorna false) adicionar uma nova chave quando já existem max chaves, enquanto as atualizações de chaves existentes continuam permitidas. Todo o restante é encaminhado com by.

O código fornecido envolve um MapStorage em um LoggingStorage e, quando a primeira linha contém um limite positivo, envolve-o também em um LimitedStorage. Ele executa os comandos put a 1, get a e keys, exibindo ok, refused, o valor ou missing, e a lista de chaves, e por fim log: put a=1, ....

Seu código deve ficar em LoggingStorage.kt, LimitedStorage.kt, Storage.kt e MapStorage.kt. Main.kt contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

fun main() {
    // Código de entrada/saída fornecido: mantenha-o 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 iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Playground de Kotlin