English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
以下示例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法。
// 创建接口 interface Base { fun print() } // 实现此接口的被委托的类 class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // 通过关键字 by 建立委托类 class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // Saída 10 }
No declaração Derived, a cláusula by indica que o b é armazenado no exemplo de objeto Derived, e o compilador gerará todos os métodos herdados do interface Base e redirecionará as chamadas para b.
A delegação de propriedade refere-se ao fato de que o valor de uma propriedade de uma classe não é definido diretamente na classe, mas é delegado a uma classe agente, permitindo assim a gestão unificada das propriedades dessa classe.
Formato da sintaxe de delegação de propriedade:
val/var <nome_da_propriedade>: <tipo> by <expressão>
var/val: tipo da propriedade (variável)/Somente leitura)
Nome da propriedade: nome da propriedade
Tipo: tipo de dados da propriedade
Expressão: classe agente delegada
pela palavra-chave by, a expressão seguinte é a delegação, o método get() (e set() para propriedades var) será delegado aos métodos getValue() e setValue() dessa instância. A delegação de propriedade não precisa implementar qualquer interface, mas deve fornecer a função getValue() (para propriedades var, também precisa da função setValue()).
Essa classe precisa conter métodos getValue() e setValue(), e o parâmetro thisRef é o objeto da classe que realiza a delegação, prop é o objeto da propriedade que realiza a delegação.
import kotlin.reflect.KProperty // Definir uma classe que contém delegação de propriedade class Example { var p: String by Delegate() } // Classe delegada class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, aqui foi delegado o atributo ${property.name}" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$thisRef 的 ${property.name} atributo atribuído a $value") } } fun main(args: Array<String>) { val e = Example() println(e.p) // Acessar essa propriedade, chamar a função getValue() e.p = "w3codebox" // Chamar a função setValue() println(e.p) }
O resultado da saída é:
Example@433c675d, aqui delegou a propriedade p Example@433c675d 的 p atributo atribuído a w3codebox Example@433c675d, aqui delegou a propriedade p
A biblioteca padrão do Kotlin já possui muitas funções fábrica para implementar delegação de propriedades.
lazy() é uma função que aceita uma expressão Lambda como parâmetro e retorna uma função que retorna uma instância de Lazy<T>, que pode ser usada como delegado para implementar propriedades adiadas: A primeira chamada get() executa a expressão Lambda passada para lazy() e grava o resultado, e as chamadas subsequentes get() retornam apenas o resultado gravado.
val lazyValue: String by lazy { println("computed!") // Primeira chamada de saída, segunda chamada não é executada "Hello" } fun main(args: Array<String>) { println(lazyValue) // Primeira execução, a expressão é executada duas vezes println(lazyValue) // Segunda execução, apenas o valor retornado é impresso }
Executar resultados de saída:
computed! Hello Hello
Observable pode ser usado para implementar o padrão Observer.
A função Delegates.observable() aceita dois parâmetros: o primeiro é o valor inicial, e o segundo é o responsável por eventos de mudança de valor de propriedade (handler).
Após a atribuição de propriedade, é executado um responsável por eventos (handler), que possui três parâmetros: a propriedade atribuída, o valor antigo e o novo valor:
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("Valor inicial") { , prop, old, new -> println("Valor antigo: $old -> Novo valor: $new } } fun main(args: Array<String>) { val user = User() user.name = "Primeira atribuição" user.name = "Segunda atribuição" }
Executar resultados de saída:
Valor antigo: Valor inicial -> Novo valor: Primeira atribuição Valor antigo: Primeira atribuição -> Novo valor: Segunda atribuição
Um caso de uso comum é armazenar valores de propriedades em um mapeamento (map). Isso ocorre frequentemente em aplicações que解析em JSON ou realizam outras ações "dinâmicas". Neste caso, você pode usar o exemplo de mapeamento próprio como delegado para implementar propriedades delegadas.
class Site(val map: Map<String, Any?>) { val name: String by map val url: String by map } fun main(args: Array<String>) { // O construtor aceita um parâmetro de mapeamento val site = Site(mapOf( "name" to "Base Tutorial Network", "url" to "www.w"3codebox.com" )) // Ler valores do mapeamento println(site.name) println(site.url) }
Executar resultados de saída:
Base Tutorial Network pt.oldtoolbag.com
Se usar propriedades var, é necessário substituir Map por MutableMap:
class Site(val map: MutableMap<String, Any?>) { val name: String by map val url: String by map } fun main(args: Array<String>) { var map: MutableMap<String, Any?> = mutableMapOf( "name" to "Base Tutorial Network", "url" to "pt.oldtoolbag.com" ) val site = Site(map) println(site.name) println(site.url) println("--------------) map.put("name", "Google") map.put("url", "www.google.com") println(site.name) println(site.url) }
Executar resultados de saída:
Base Tutorial Network pt.oldtoolbag.com -------------- Google www.google.com
NotNull é aplicável a situações onde o valor da propriedade não pode ser determinado no estágio de inicialização.
class Foo { var notNullBar: String by Delegates.notNull<String>() } foo.notNullBar = "bar" println(foo.notNullBar)
Deve-se notar que, se a propriedade for acessada antes de ser atribuída, uma exceção será lançada.
Você pode declarar variáveis locais como propriedades de delegação. Por exemplo, você pode inicializar uma variável local de forma perezosa:
fun example(computeFoo: () -> -> Foo) { val memoizedFoo by lazy(computeFoo) if (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() } }
A variável memoizedFoo será calculada apenas na primeira vez que for acessada. Se someCondition falhar, a variável nunca será calculada.
Para propriedades de leitura apenas (isto é, a propriedade val), o delegado deve fornecer uma função chamada getValue(). Essa função aceita os seguintes parâmetros:
thisRef - deve ser o mesmo tipo ou superTipo do proprietário da propriedade (para propriedades de extensão - o tipo estendido)
property - deve ser do tipo KProperty ou seu superTipo
Esta função deve retornar o mesmo tipo (ou subtipo) da propriedade.
Para uma propriedade mutável (var), além da função getValue(), sua delegação deve fornecer outra função chamada setValue(), que aceita os seguintes parâmetros:
thisRef - deve ser o mesmo tipo ou superTipo do proprietário da propriedade (para propriedades de extensão - o tipo estendido)
property - deve ser do tipo KProperty ou seu superTipo
new value - deve ser do mesmo tipo ou subtipo da propriedade.
Por trás de cada implementação de propriedade delegada, o compilador do Kotlin gera propriedades auxiliares e as delega para elas. Por exemplo, para a propriedade prop, gera a propriedade oculta prop$delegate, e o código do acessor simplesmente delega para essa propriedade adicional:
class C { var prop: Type by MyDelegate() } // Este código é gerado pelo compilador correspondente: class C { private val prop$delegate = MyDelegate() var prop: Type get() = prop$delegate.getValue(this, this::prop) set(value: Type) = prop$delegate.setValue(this, this::prop, value) }
O compilador do Kotlin fornece todas as informações necessárias sobre o prop nos parâmetros: o primeiro parâmetro this faz referência ao exemplo da classe externa C e this::prop é um objeto reflexivo do tipo KProperty, que descreve o prop próprio.
Ao definir o operador provideDelegate, você pode estender a lógica de criação do objeto delegado na implementação da criação de propriedades. Se o objeto usado à direita do by definir provideDelegate como membro ou função de extensão, a função será chamada para criar um exemplo de delegação de propriedade.
Um dos possíveis cenários de uso do provideDelegate é verificar a consistência da propriedade ao criar a propriedade (e não apenas no getter ou setter).
Por exemplo, se você quiser verificar o nome da propriedade antes de ligar, você pode escrever assim:
class ResourceLoader<T>(id: ResourceID<T>) { operador fun provideDelegate( thisRef: MyUI, prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> { checkProperty(thisRef, prop.name) // Criar delegação } private fun checkProperty(thisRef: MyUI, name: String) { …… } } fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… } class MyUI { val image by bindResource(ResourceID.image_id) val text by bindResource(ResourceID.text_id) }
Os parâmetros do provideDelegate são os mesmos que os do getValue:
thisRef —— deve ser o mesmo tipo que o proprietário da propriedade (para propriedades de extensão, o tipo que é estendido)
property —— deve ser do tipo KProperty ou seu superTipo.
Durante a criação de um exemplo de MyUI, chame o método provideDelegate para cada propriedade e execute imediatamente a verificação necessária.
Se não houver a capacidade de interceptar o vínculo entre a propriedade e seu delegado, para implementar a mesma funcionalidade, você deve passar explicitamente o nome da propriedade, o que não é muito conveniente:
// Verificar o nome da propriedade sem usar a funcionalidade 'provideDelegate' class MyUI { val image by bindResource(ResourceID.image_id, "image") val text by bindResource(ResourceID.text_id, "text") } fun <T> MyUI.bindResource( id: ResourceID<T>, propertyName: String ): ReadOnlyProperty<MyUI, T> { checkProperty(this, propertyName) // Criar delegação }
No código gerado, o método provideDelegate é chamado para inicializar a propriedade auxiliar prop$delegate. Compare o código gerado para a declaração de propriedade val prop: Type by MyDelegate() com o código acima (quando o método provideDelegate não existe):
class C { var prop: Type by MyDelegate() } // Este código é executado quando a funcionalidade 'provideDelegate' está disponível // Código gerado pelo compilador: class C { // Chame provideDelegate para criar uma propriedade adicional de 'delegate' private val prop$delegate = MyDelegate().provideDelegate(this, this::prop) val prop: Type get() = prop$delegate.getValue(this, this::prop) }
Por favor, note que o método provideDelegate afeta apenas a criação de propriedades auxiliares, não o código gerado para getter ou setter.