Kotlin delegation
Delegation pattern is a basic skill in software design pattern. In delegate mode, two objects participate in processing the same request, and the objectthat accepts the request delegates the request to another object to process.
Kotlin
directly support the delegation model, more elegant and concise. Kotlin
by keyword by
implement the delegate.
Class delegation
The delegate of a class, that is, a method defined in one class is actually implemented by calling the method of an object of another class.
The derived class in the following example Derived
inherited the interface Base
all methods and delegate an incoming Base
class to execute these methods.
// Create Interface
interface Base {
fun print()
}
// The delegated class that implements this interface
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// Establishing a delegate class through the keyword by
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // Output 10
}
In Derived
statement by
clause indicates that the b
save in Derived
and the compiler will generate the object instance inherited from the Base
interface and forwards the call to the b
.
Attribute delegation
Attribute delegation means that the attribute value of a class is not defined directly in the class, but entrusted to a proxy class, so as to realize the unified management of the attributes of this class.
Attribute delegate syntax format:
val/var <Attribute Name>:<Type>by<Expression>
var/val
: Attribute type (variable/read-only)Attribute name: attribute name
Type: data type of the attribute
Expressions: delegated agent class
by
keyword is the delegate, the attribute get()
method (and set()
method to be delegated to this object getValue()
and setValue()
method. Property delegates do not have to implement any interfaces, but must provide getValue()
function (for var
property, you also need to setValue()
function).
Define a delegated class
This class needs to contain getValue()
methods and setValue()
method and parameters thisRef
is the object of the delegated class prop
is the object of the delegated property.
import kotlin.reflect.KProperty
// Define a class that contains attribute delegates
class Example {
var p: String by Delegate()
}
// Delegated Class
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, Here, the ${property. name} attribute is delegated"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$The ${property.name} attribute of thisRef is assigned a value of $value")
}
}
fun main(args: Array<String>) {
val e = Example()
println(e.p) // Access this property and call the getValue() function
e.p = "Runoob" // Calling the setValue() function
println(e.p)
}
The output is as follows:
Example@433c675d Here, the p attribute is delegated
Example@433c675d Assign the p attribute of to Runoob
Example@433c675d Here, the p attribute is delegated
Standard entrustment
A number of factory methods have been built into the standard Kotlin
library to implement the delegate of properties.
Delay property Lazy
lazy()
is a function that accepts a Lambda
expression as an argument, returns a Lazy <T>
instance, and the returned instance can beused as a delegate to implement the delay property: call the get()
execution has been passed to the lazy()
of lamda
expression and record the result, and then call get()
just return the results of the record.
val lazyValue: String by lazy {
println("computed!") // First call output, second call not executed
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue) // First execution, execute the output expression twice
println(lazyValue) // The second execution only outputs the return value
}
Execute the output result:
computed!
Hello
Hello
Observable attribute Observable
observable
can be used to implement the observer pattern.
Delegates.observable()
function takes two arguments: the first is the initialization value, and the second is the responder (handler) to the property value change event.
The handler that executes the event after the attribute assignment, which has three parameters: the assigned attribute, the old value, and the new value:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("Initial value") {
prop, old, new ->
println("old value:$old -> new value:$new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "First assignment"
user.name = "Second assignment"
}
Execute the output result:
Old value: Initial value ->New value: First assignment
Old value: First assignment ->New value: Second assignment
Store attributes in the mapping
A common use case is to store the value of an attribute in a map. This oftenoccurs in applications like parsing JSON or doing other “dynamic” things.In this case, you can use the mapping instance itself as a delegate to implement the delegate property.
class Site(val map: Map<String, Any?>) {
val name: String by map
val url: String by map
}
fun main(args: Array<String>) {
// Constructor accepts a mapping parameter
val site = Site(mapOf(
"name" to "Novice Tutorial",
"url" to "www.runoob.com"
))
// Read mapping values
println(site.name)
println(site.url)
}
Execute the output result:
Novice Tutorial
www.runoob.com
If you use the var
property, you need to set the Map
replace it with 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 "Novice Tutorial",
"url" to "www.runoob.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)
}
Execute the output result:
Novice Tutorial
www.runoob.com
--------------
Google
www.google.com
Not Null
notNull
is suitable for situations where the attribute value cannot be determined at the initialization stage.
class Foo {
var notNullBar: String by Delegates.notNull<String>()
}
foo.notNullBar = "bar"
println(foo.notNullBar)
It is important to note that an exception is thrown if the property is accessed before the assignment.
Local delegate attribute
You can declare local variables as delegate properties. For example, you caninitialize a local variable lazily:
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
memoizedFoo
variables are only evaluated on the first access. If someCondition
if it fails, the variable will not be calculated at all.
Attribute delegation requirement
For a read-only property (that is, the val property), its delegate must provide a file named getValue()
function. This function accepts the following parameters:
thisRef
- must be the same as the property owner type (for extended attributes - the type being extended) or its supertypeproperty
- must be a typeKProperty<*>
or its supertype.
This function must return the same type (or its subtype) as the property.
For a variable value (mutable) attribute (that is, var
property), except getValue()
function, its delegate must provide another one named setValue()
which accepts the following parameters:
property
- must be a typeKProperty<*>
or its supertype.new value
must be of the same type as the property or its supertype.
Translation rules
Behind the implementation of each delegate property Kotlin
compilergenerates an auxiliary property and delegates it. For example, for attributes prop
to generate hidden properties prop$delegate
and the code for the accessor is simply delegated to this additional property
class C {
var prop: Type by MyDelegate()
}
// This is the corresponding code generated by the compiler:
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
compiler provides information in the parameters about prop
all the necessary information: the first parameter this
reference to aninstance of the external class C and this::prop
KProperty
reflection object of type that describes the prop
himself.
Provide entrustment
By defining provideDelegate
operator, you can extend the logic of creating properties to implement the delegated object. If by
objectusedon the right will provideDelegate
if it is defined as a member oran extension function, the function is called to create a property delegateinstance.
provideDelegate
possible usage scenario is when a property is created (not just in its getter
or setter
check for property consistency.
For example, if you want to check the property name before binding, you can write:
class ResourceLoader<T>(id: ResourceID<T>) {
operator fun provideDelegate(
thisRef: MyUI,
prop: KProperty<*>
): ReadOnlyProperty<MyUI, T> {
checkProperty(thisRef, prop.name)
// Create Delegation
}
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)
}
provideDelegate
parameters same as getValue
:
thisRef
- must be the same as the property owner type (for extended attributes - the type being extended) or its supertypeproperty
- must be a typeKProperty<*>
or its supertype.
Before creating MyUI
instance, call for each property provideDelegate
method and immediately perform the necessary validation.
Without this ability to intercept the binding between a property and its delegate, you must explicitly pass the property name in order to achieve thesame function, which is not very convenient:
// Check attribute names without using the 'provideDelegate' function
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)
// Create Delegation
}
In the generated code, the provideDelegate
method to initialize the auxiliary prop$delegate
property. Compare for attribute declarations val prop: Type by MyDelegate()
generated code is the same as above (when provideDelegate
code generated when the method does not exist:
class C {
var prop: Type by MyDelegate()
}
// This code is when the "provideDelegate" function is available
// Code generated by the compiler:
class C {
// call “provideDelegate” to create additional “delegate“ attributes
private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
val prop: Type
get() = prop$delegate.getValue(this, this::prop)
}
Attention please, provideDelegate
method only affects the creation of auxiliary properties and does not affect the getter
or setter
generated code.