English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Le modèle de délégation est une technique fondamentale du modèle de conception du logiciel. Dans le modèle de délégation, deux objets participent au traitement de la même demande, et l'objet recevant la demande délègue la demande à un autre objet pour le traitement.
Kotlin supporte directement le modèle de délégation, ce qui est plus élégant et plus simple. Kotlin effectue la délégation par le mot-clé by.
La délégation de classe consiste à ce que la méthode définie dans une classe appelle réellement la méthode d'une autre instance d'objet.
dans l'exemple suivant, la classe dérivée Derived hérite de toutes les méthodes de l'interface Base et délègue une instance de la classe Base transmise pour exécuter ces méthodes.
// créer une interface interface Base { fun print() } // implémenter l'interface déléguée de cette classe class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // par le mot-clé by, on établit une classe déléguée class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // Sortie 10 }
Dans la déclaration Derived, la clause by indique que b est stocké dans l'exemple d'objet de Derived, et le compilateur générera tous les méthodes héritées de l'interface Base, et redirigera les appels vers b.
La délégation d'attribut fait référence à ce que la valeur d'une propriété d'une classe n'est pas définie directement dans la classe, mais est déléguée à une classe代理, permettant ainsi une gestion unifiée des attributs de la classe.
Format de syntaxe de délégation d'attribut :
val/var <nom_de_l'attribut>: <type> by <expression>
var/val : type de l'attribut (modifiable/Lecture seule)
Nom de l'attribut : nom de l'attribut
Type : type des données de l'attribut
Expression : classe déléguée
La expression après la clé by est la délégation, les méthodes get() (et set() pour les propriétés var) des attributs seront déléguées aux méthodes getValue() et setValue() de cet objet. Les délégués d'attributs n'ont pas besoin d'implémenter d'interface, mais doivent fournir la fonction getValue() (et la fonction setValue() pour les propriétés var).
Cette classe doit contenir les méthodes getValue() et setValue(), et le paramètre thisRef est l'objet de la classe déléguée, prop est l'objet de l'attribut délégué.
import kotlin.reflect.KProperty // Définir une classe contenant des délégués d'attributs class Example { var p: String by Delegate() } // Classe déléguée class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, ici, ${property.name} a été délégué" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$thisRef, l'attribut ${property.name} est assigné à $value") } } fun main(args: Array<String>) { val e = Example() println(e.p) // Accéder à cette propriété, appeler la fonction getValue() e.p = "w3codebox" // Appel de la fonction setValue() println(e.p) }
Le résultat de la sortie est :
Example@433c675d, ici le délégué est l'attribut p Example@433c675l'attribut p de d est assigné à w3codebox Example@433c675d, ici le délégué est l'attribut p
La bibliothèque standard de Kotlin contient déjà de nombreuses méthodes d'usine pour implémenter les délégués de propriété.
lazy() est une fonction qui prend une expression Lambda en paramètre et retourne une fonction qui retourne un exemple de Lazy <T>, l'exemple retourné peut être utilisé comme délégué pour implémenter les propriétés différées : la première appel de get() exécute l'expression Lambda passée à lazy() et enregistre le résultat, et les appels ultérieurs à get() ne retournent que le résultat enregistré.
val lazyValue: String by lazy { println("computed!") // Première appel, deuxième appel ne s'exécute pas "Hello" } fun main(args: Array<String>) { println(lazyValue) // Première exécution, exécution de l'expression deux fois println(lazyValue) // Deuxième exécution, uniquement affichage de la valeur de retour }
Résultat de l'exécution :
computed! Hello Hello
Observable peut être utilisé pour implémenter le modèle observateur.
La fonction Delegates.observable() accepte deux paramètres : le premier est la valeur initiale, et le deuxième est le récepteur d'événement (handler) de la modification de la valeur de l'attribut.
Après l'assignation de l'attribut, un récepteur d'événement (handler) est exécuté, qui prend trois paramètres : l'attribut assigné, la valeur ancienne et la nouvelle valeur :
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("valeur initiale") { , prop, old, new -> println("Ancienne valeur : $old -> Nouvelle valeur : $new" } } fun main(args: Array<String>) { val user = User() user.name = "assignation pour la première fois" user.name = "assignation pour la deuxième fois" }
Résultat de l'exécution :
Ancienne valeur : valeur initiale -> Nouvelle valeur : assignation pour la première fois Ancienne valeur : assignation pour la première fois -> Nouvelle valeur : assignation pour la deuxième fois
Un cas d'utilisation courant consiste à stocker les valeurs des attributs dans une carte (map). Cela se voit souvent dans des applications telles que l'analyse JSON ou d'autres actions "dynamiques". Dans ce cas, vous pouvez utiliser l'exemple de la carte elle-même en tant que délégué pour implémenter les attributs délégués.
class Site(val map: Map<String, Any?>) { val name: String by map val url: String by map } fun main(args: Array<String>) { // Le constructeur accepte un paramètre de mapping val site = Site(mapOf( "name" to "Réseau de bases", "url" to "www.w"}}3codebox.com" )) // Lecture des valeurs de la carte println(site.name) println(site.url) }
Résultat de l'exécution :
Réseau de bases fr.oldtoolbag.com
Si vous utilisez la propriété var, vous devez remplacer Map par 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 "Réseau de bases", "url" to "fr.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) }
Résultat de l'exécution :
Réseau de bases fr.oldtoolbag.com -------------- Google www.google.com
NotNull est applicable aux cas où la valeur de la propriété ne peut pas être déterminée au stade d'initialisation.
class Foo { var notNullBar: String by Delegates.notNull<String>() } foo.notNullBar = "bar" println(foo.notNullBar)
Il convient de noter que si la propriété est consultée avant son assignation, une exception est lancée.
Vous pouvez déclarer des variables locales en tant que propriétés déléguées. Par exemple, vous pouvez initialiser une variable locale de manière paresseuse :
fun example(computeFoo: () -> -> Foo) { val memoizedFoo by lazy(computeFoo) if (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() } }
La variable memoizedFoo ne sera calculée que lors du premier accès. Si someCondition échoue, cette variable ne sera pas calculée du tout.
Pour les propriétés en lecture seule (c'est-à-dire la propriété val), le délégué doit fournir une fonction nommée getValue(). Cette fonction accepte les paramètres suivants :
thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
property —— 必须是类型 KProperty 或其超类型
这个函数必须返回与属性相同的类型(或其子类型)。
对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须另外再提供一个名为 setValue()的函数,这个函数接受以下参数:
thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
property —— 必须是类型 KProperty 或其超类型
new value —— 必须和属性同类型或者是它的超类型。
在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:
class C { var prop: Type by MyDelegate() } // 这段是由编译器生成的相应代码: 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) }
Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的示例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。
通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来创建属性委托示例。
provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检查属性一致性。
例如,如果要在绑定之前检查属性名称,可以这样写:
class ResourceLoader<T>(id: ResourceID<T>) { operator fun provideDelegate( thisRef: MyUI, prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> { checkProperty(thisRef, prop.name) // Création de l'intermédiaire } 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) }
Les paramètres de provideDelegate sont identiques à ceux de getValue :
thisRef —— doit être du même type que le propriétaire de l'attribut (pour les propriétés d'extension — le type étendu) ou un de ses types parent
property —— doit être de type KProperty ou un de ses types parent.
Pendant la création de l'exemple MyUI, appeler la méthode provideDelegate pour chaque propriété et exécuter immédiatement la vérification nécessaire.
Si vous n'avez pas la capacité d'intercepter le lien entre la propriété et son mandataire, pour réaliser la même fonction, vous devez explicitement passer le nom de la propriété, ce qui n'est pas très pratique :
// Vérification du nom de propriété sans utiliser la fonction "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) // Création de l'intermédiaire }
Dans le code généré, la méthode provideDelegate est appelée pour initialiser l'attribut prop$delegate assistant. Comparez le code généré pour la déclaration d'attribut val prop: Type by MyDelegate() avec le code généré ci-dessus (quand la méthode provideDelegate n'existe pas) :
class C { var prop: Type by MyDelegate() } // Ce code est utilisé lorsque la fonction "provideDelegate" est disponible : // Le code généré par le compilateur : class C { // Appeler "provideDelegate" pour créer des attributs "delegate" supplémentaires private val prop$delegate = MyDelegate().provideDelegate(this, this::prop) val prop: Type get() = prop$delegate.getValue(this, this::prop) }
Veuillez noter que la méthode provideDelegate ne影响了辅助属性的创建,并不会影响为 getter 或 setter 生成的代码。