Propriedades delegadas em Kotlin

Propriedades delegadas em Kotlin

1. Introdução

A linguagem de programação Kotlin possui suporte nativo para propriedades de classe.

As propriedades geralmente são apoiadas diretamente pelos campos correspondentes, mas nem sempre precisam ser assim - desde que sejam corretamente expostas ao mundo externo, elas ainda podem ser consideradas propriedades.

Isso pode ser alcançado tratando-se disso em getters e setters, ou aproveitando o poder deDelegates.

2. O que são propriedades delegadas?

Simply put, delegated properties are not backed by a class field and delegate getting and setting to another piece of code. Isso permite que a funcionalidade delegada seja abstraída e compartilhada entre várias propriedades semelhantes - por exemplo, armazenando valores de propriedades em um mapa em vez de campos separados.

Propriedades delegadas são usadas declarando a propriedade e o delegado que ela usa. The by keyword indicates that the property is controlled by the provided delegate instead of its own field.

Por exemplo:

class DelegateExample(map: MutableMap) {
    var name: String by map
}

Isso usa o fato de que aMutableMap é ele próprio um delegado, permitindo que você trate suas chaves como propriedades.

3. Propriedades delegadas padrão

A biblioteca padrão Kotlin vem com um conjunto de delegados padrão que estão prontos para serem usados.

Já vimos um exemplo de uso deMutableMap para apoiar uma propriedade mutável. Da mesma forma, você pode respaldar uma propriedade imutável usando umMap - permitindo que campos individuais sejam acessados ​​como propriedades, mas nunca os altere.

The lazy delegate allows the value of a property to be computed only on first access and then cached. Isso pode ser útil para propriedades que podem ser caras para calcular e que você nem precisa - por exemplo, ser carregado de um banco de dados:

class DatabaseBackedUser(userId: String) {
    val name: String by lazy {
        queryForValue("SELECT name FROM users WHERE userId = :userId", mapOf("userId" to userId)
    }
}

The observable delegate allows for a lambda to be triggered any time the value of the property changes, por exemplo, permitindo notificações de mudança ou atualização de outras propriedades relacionadas:

class ObservedProperty {
    var name: String by Delegates.observable("") {
        prop, old, new -> println("Old value: $old, New value: $new")
    }
}

4. Criando Seus Delegados

Haverá momentos em que você deseja escrever para seus delegados, em vez de usar os que já existem. This relies on writing a class that extends one of two interfaces – ReadOnlyProperty or ReadWriteProperty.

Ambas as interfaces definem um método chamadogetValue - que é usado para fornecer o valor atual da propriedade delegada quando ela é lida. Isso leva dois argumentos e retorna o valor da propriedade:

  • thisRef - uma referência à classe em que a propriedade está

  • property - uma descrição de reflexão da propriedade que está sendo delegada

A interfaceReadWriteProperty adicionalmente define um método chamadosetValue que é usado para atualizar o valor atual da propriedade quando ela é escrita. Isso leva três argumentos e não tem valor de retorno:

  • thisRef - Uma referência à classe em que a propriedade está

  • property - Uma descrição de reflexão da propriedade que está sendo delegada

  • value - O novo valor da propriedade

Como exemplo, vamos escrever um delegado que sempre funciona em relação a uma conexão de banco de dados em vez de campos locais:

class DatabaseDelegate(readQuery: String, writeQuery: String, id: Any) : ReadWriteDelegate {
    fun getValue(thisRef: R, property: KProperty<*>): T {
        return queryForValue(readQuery, mapOf("id" to id))
    }

    fun setValue(thisRef: R, property: KProperty<*>, value: T) {
        update(writeQuery, mapOf("id" to id, "value" to value))
    }
}

Isso depende de duas funções de nível superior para acessar o banco de dados:

  • queryForValue - leva algum SQL e alguns vínculos e retorna o primeiro valor

  • update - isso pega um pouco de SQL e alguns vínculos e o trata como uma instrução UPDATE

Podemos então usar isso como qualquer delegado comum e ter nossa classe automaticamente apoiada pelo banco de dados:

class DatabaseUser(userId: String) {
    var name: String by DatabaseDelegate(
      "SELECT name FROM users WHERE userId = :id",
      "UPDATE users SET name = :value WHERE userId = :id",
      userId)
    var email: String by DatabaseDelegate(
      "SELECT email FROM users WHERE userId = :id",
      "UPDATE users SET email = :value WHERE userId = :id",
      userId)
}

5. Sumário

A delegação de propriedades é uma técnica poderosa, que permite escrever código que assume o controle de outras propriedades e ajuda a compartilhar facilmente essa lógica entre diferentes classes. Isso permite uma lógica robusta e reutilizável que se parece com o acesso regular à propriedade.

Um exemplo totalmente funcional para este artigo pode ser encontradoover on GitHub.