English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Das Delegationmuster ist eine grundlegende Technik im Bereich der Software-Designmuster. Bei der Delegation beteiligen sich zwei Objekte an der Verarbeitung desselben Antrags, wobei das Antragsobjekt den Antrag an ein anderes Objekt weiterleitet, um ihn zu bearbeiten.
Kotlin unterstützt das Delegationmuster direkt, was eleganter und einfacher ist. Kotlin implementiert die Delegation durch das Schlüsselwort by.
Klassendelegation bedeutet, dass eine Methode in einer Klasse tatsächlich durch Aufruf der Methode eines anderen Objekts implementiert wird.
Im folgenden Beispiel übernimmt die abgeleitete Klasse Derived alle Methoden des Interfaces Base und delegiert die Ausführung dieser Methoden an ein übergebenes Objekt der Klasse Base.
// Erstellung eines Interfaces interface Base { fun print() } // Implementierung der von diesem Interface abgeleiteten Delegierklasse class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // Durch den Schlüsselwort by wird eine Delegierklasse erstellt class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10 Derived(b).print() // 输出 10 }
在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象示例内部,而且编译器将会生成继承自 Base 接口的所有方法,并将调用转发给 b。
属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。
属性委托语法格式:
val/var <属性名>: <类型> by <表达式>
var/val:属性类型(可变/只读)
属性名:属性名称
类型:属性的数据类型
表达式:委托代理类
by 关键字之后的表达式就是委托, 属性的 get() 方法(以及set() 方法)将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口,但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。
该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,prop 为进行委托的属性的对象。
import kotlin.reflect.KProperty // 定义包含属性委托的类 class Example { var p: String by Delegate() } // 委托的类 class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*): String { return "$thisRef, 这里委托了 ${property.name} 属性" } operator fun setValue(thisRef: Any?, property: KProperty<*, value: String) { println("$thisRef 的 ${property.name} 属性被赋值为 $value") } } fun main(args: Array<String>) { val e = Example() println(e.p) // 访问该属性,调用 getValue() 函数 e.p = "w3codebox" // 调用 setValue() 函数 println(e.p) }
输出结果为:
Example@433c675d, hier wird das Attribut p delegiert Example@433c675d 的 p 属性被赋值为 w3codebox Example@433c675d, hier wird das Attribut p delegiert
Die Standardbibliothek von Kotlin enthält viele Factory-Methoden, um die Delegation von Attributen zu realisieren.
lazy() ist eine Funktion, die einen Lambda-Ausdruck als Parameter akzeptiert und eine Funktion zurückgibt, die ein Beispiel von Lazy<T> zurückgibt. Dieses Beispiel kann als Delegat für verlangsamte Attribute verwendet werden: Beim ersten Aufruf von get() wird der übergebene Lambda-Ausdruck ausgeführt und das Ergebnis gespeichert, bei nachfolgenden Aufrufen von get() wird nur das gespeicherte Ergebnis zurückgegeben.
val lazyValue: String by lazy { println("berechnet!") // Erste Aufrufausgabe, zweiter Aufruf wird nicht ausgeführt "Hallo" } fun main(args: Array<String>) { println(lazyValue) // Erste Ausführung, Ausgabe des Ausdrucks zweimal println(lazyValue) // Zweite Ausführung, nur der Rückgabewert wird ausgegeben }
Ausführungsergebnis:
berechnet! Hallo Hallo
Observable kann verwendet werden, um das Observer-Pattern zu realisieren.
Die Funktion Delegates.observable() akzeptiert zwei Parameter: Der erste ist der Initialwert, der zweite ist der Ereignis-Handler für die Änderung der Attributswerte.
Ein Ereignis-Handler wird nach der Zuweisung eines Attributs ausgeführt, der drei Parameter hat: das zugewiesene Attribut, den alten Wert und den neuen Wert:
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("Initialwert") { , prop, old, new -> println("Alter Wert: $old -> Neuer Wert: $new" } } fun main(args: Array<String>) { val user = User() user.name = "Erste Zuweisung" user.name = "Zweite Zuweisung" }
Ausführungsergebnis:
Alter Wert: Initialwert -> Neuer Wert: Erste Zuweisung Alter Wert: Erste Zuweisung -> Neuer Wert: Zweite Zuweisung
Ein häufiger Anwendungsfall ist die Speicherung von Attributswerten in einer Abbildung (map). Dies tritt oft in Anwendungen auf, die JSON analysieren oder andere "dynamische" Aktionen durchführen. In diesem Fall können Sie die Beispielabbildung selbst als Delegat verwenden, um Delegatenschaften zu realisieren.
class Site(val map: Map<String, Any?>) { val name: String by map val url: String by map } fun main(args: Array<String>) { // Der Konstruktor akzeptiert einen Mapping-Parameter val site = Site(mapOf( "name" to "Grundlagen-Tutorial-Website", "url" to "www.w"3codebox.com" )) // Lesen Sie den Wert des Mappings println(site.name) println(site.url) }
Ausführungsergebnis:
Grundlagen-Tutorial-Website de.oldtoolbag.com
Wenn var-Eigenschaften verwendet werden, muss das Map in MutableMap geändert werden:
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 "Grundlagen-Tutorial-Website", "url" to "de.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) }
Ausführungsergebnis:
Grundlagen-Tutorial-Website de.oldtoolbag.com -------------- Google www.google.com
NotNull ist für Fälle geeignet, in denen der Wert der Eigenschaft nicht bereits in der Initialisierungsphase bestimmt werden kann.
class Foo { var notNullBar: String by Delegates.notNull<String>() } foo.notNullBar = "bar" println(foo.notNullBar)
Zu beachten ist, dass eine Ausnahme ausgelöst wird, wenn eine Eigenschaft vor der Zuweisung aufgerufen wird.
Sie können lokale Variablen als Delegateneigenschaften deklarieren. Zum Beispiel können Sie eine lokale Variable lazy initialisieren:
fun example(computeFoo: () -> Foo) { val memoizedFoo by lazy(computeFoo) if (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() } }
Die Variable memoizedFoo wird nur bei der ersten Anfrage berechnet. Wenn someCondition fehlschlägt, wird diese Variable überhaupt nicht berechnet.
Für nur lesbare Eigenschaften (das heißt val-Eigenschaft) muss der Delegat eine Funktion namens getValue() bereitstellen. Diese Funktion akzeptiert die folgenden Parameter:
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) // Erstellen Sie einen Delegaten } 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) }
Die Parameter von provideDelegate sind gleich denen von getValue:
thisRef - muss mit dem Typ des Attributinhabers (bei erweiterten Eigenschaften - dies ist der erweiterte Typ) identisch oder ein Super Typ davon sein
property - muss vom Typ KProperty oder einem seiner Super Typen sein.
Rufen Sie während der Erstellung eines MyUI-Beispiels die Methode provideDelegate für jede Eigenschaft auf und führen Sie sofort die erforderlichen Validierungen durch.
Wenn die Fähigkeit, die Bindung zwischen dem拦截属性 und seinem Delegaten zu unterbrechen, nicht vorhanden ist, müssen Sie zur Erreichung desselben Funktionsumfangs die Attributnamen explizit übergeben, was nicht sehr bequem ist:
// Überprüfen Sie den Attributnamen ohne die Funktion 'provideDelegate' zu verwenden 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) // Erstellen Sie einen Delegaten }
Im generierten Code wird die Methode provideDelegate aufgerufen, um die辅助的 prop$delegate-Eigenschaft zu initialisieren. Vergleichen Sie den Code für die Eigenschaftserklärung val prop: Type by MyDelegate() mit dem obigen Code (wenn die Methode provideDelegate nicht existiert):
class C { var prop: Type by MyDelegate() } // Dieser Code ist verfügbar, wenn die Funktion 'provideDelegate' aktiviert ist // Von Compiler generierter Code: class C { // Rufen Sie "provideDelegate" auf, um zusätzliche "delegate"-Attribute zu erstellen private val prop$delegate = MyDelegate().provideDelegate(this, this::prop) val prop: Type get() = prop$delegate.getValue(this, this::prop) }
Bitte beachten Sie, dass die Methode provideDelegate nur die Erstellung von Hilfsattributen beeinflusst und nicht den Code beeinflusst, der für Getter oder Setter generiert wird.