Kotlin delegation


Release date:2023-09-26 Update date:2023-10-13 Editor:admin View counts:268

Label:

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 supertype

  • property - must be a type KProperty<*> 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 type KProperty<*> 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 supertype

  • property - must be a type KProperty<*> 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.

Powered by TorCMS (https://github.com/bukun/TorCMS).