Kotlin learning: commissioned by Kotlin

kotlin commission

The commission mode is a basic skill in the software design mode. In the delegation mode, there are two objects involved in processing the same request, and the object accepting the request delegates the request to another object for processing.

Kotlin directly supports the delegation mode, which is more elegant and concise. Kotlin implements delegation through the keyword by

Class delegate

Class delegation means that the methods defined in one class are actually implemented by calling methods of objects of another class.

In the following example, the derived class Derived inherits all methods of the interface Base, and delegates an object of the passed Base class to execute these methods

// 创建接口
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() // 输出 10
}

In the Derived declaration, the by clause indicates that b is stored inside the Derived object instance, and the compiler will generate all methods inherited from the Base interface and forward the call to b

Property delegation

Attribute delegation means that a certain attribute value of a class is not directly defined in the class, but is entrusted to an agent class, so as to realize the unified management of the properties of the class.

Attribute delegation syntax format:

val/var <属性名>: <类型> by <表达式>
  • var/val: attribute type (variable/read-only)
  • Property name: Property name
  • Type: The data type of the attribute
  • Expression: Principal-agent class

The expression after the by keyword is a delegation, and the get() method (and set() method) of the property will be delegated to the getValue() and setValue() methods of this object. The attribute delegate does not need to implement any interface, but must provide the getValue() function (for var attributes, the setValue() function is also required)

Define a delegated class

This class needs to include the getValue() method and the setValue() method, and the parameter thisRef is the object of the delegated class, and prop is the object of the delegated property

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 = "Runoob"   // 调用 setValue() 函数
    println(e.p)
}

The output result is

Example@433c675d, 这里委托了 p 属性
Example@433c675d 的 p 属性赋值为 Runoob
Example@433c675d, 这里委托了 p 属性

Standard commission

Kotlin's standard library has built-in many factory methods to implement property delegation

Lazy properties

lazy() is a function that accepts a Lambda expression as a parameter and returns a Lazy<T> instance. The returned instance can be used as a delegate to implement delayed properties: the first call to get() will execute the function that has been passed to lazy( ) Lamda expression and record the result, subsequent calls to get() just return the recorded result

val lazyValue: String by lazy {
    println("computed!")     // 第一次调用输出,第二次调用不执行
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)   // 第一次执行,执行两次输出表达式
    println(lazyValue)   // 第二次执行,只输出返回值
}

Execution output result

computed!
Hello
Hello

Observable

observable can be used to implement the observer pattern.

The Delegates.observable() function accepts two parameters: the first is the initial value, and the second is the handler for the property value change event.

After the attribute is assigned, the event handler will be executed. It 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("初始值") {
        prop, old, new ->
        println("旧值:$old -> 新值:$new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "第一次赋值"
    user.name = "第二次赋值"
}

Execution output result:

旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值

Store attributes in the map

A common use case is to store the value of an attribute in a map. This often occurs 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 delegated properties.

class Site(val map: Map<String, Any?>) {
    val name: String by map
    val url: String  by map
}

fun main(args: Array<String>) {
    // 构造函数接受一个映射参数
    val site = Site(mapOf(
        "name" to "菜鸟教程",
        "url"  to "www.runoob.com"
    ))
    
    // 读取映射值
    println(site.name)
    println(site.url)
}

Execution output result

菜鸟教程
www.runoob.com

If you use the var attribute, you need to change Map to 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 "菜鸟教程",
            "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)

}

Execution output result

菜鸟教程
www.runoob.com
--------------
Google
www.google.com

Not Null

notNull is suitable for those occasions where the attribute value cannot be determined in the initialization phase

class Foo {
    var notNullBar: String by Delegates.notNull<String>()
}

foo.notNullBar = "bar"
println(foo.notNullBar)

It should be noted that if the property is accessed before the assignment, an exception will be thrown

Local delegated properties

You can declare local variables as delegated properties. For example, you can make a local variable lazily initialized

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

The memoizedFoo variable will only be calculated the first time it is accessed. If someCondition fails, then the variable will not be calculated at all

Property delegation requirements

For read-only properties (that is, val properties), its delegate must provide a function called getValue(). The function accepts the following parameters:

  • thisRef-must be the same as the attribute owner type (for extended attributes-the type being extended) or its supertype
  • property-must be of type KProperty<*> or its supertype

This function must return the same type (or its subtype) as the attribute.

For a mutable attribute (that is, var attribute), in addition to the getValue() function, its delegate must also provide another function named setValue(), which accepts the following parameters:

  • property-must be of type KProperty<*> or its supertype
  • new value-must be the same type as the attribute or its supertype.

Translation rules

Behind the implementation of each delegated property, the Kotlin compiler generates auxiliary properties and delegates to it. For example, for the property prop, a hidden property prop$delegate is generated, and the code of the accessor simply delegates to this additional property

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)
}

The Kotlin compiler provides all necessary information about prop in the parameters: the first parameter this refers to an instance of the external class C and this::prop is a reflection object of type KProperty, which describes the prop itself

Offer commission

By defining the provideDelegate operator, you can extend the creation of attributes to implement the logic of the delegated object. If the object used on the right side of by defines provideDelegate as a member or extension function, then that function will be called to create an instance of the property delegate.

One possible use case for provideDelegate is to check property consistency when creating a property (not just in its getter or setter).

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)
        // 创建委托
    }

    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)
}

The parameters of provideDelegate are the same as getValue:

  • thisRef-must be the same as the attribute owner type (for extended attributes-the type being extended) or its supertype
  • property-must be of type KProperty<*> or its supertype.

During the creation of the MyUI instance, the provideDelegate method is called for each property and the necessary validation is performed immediately.

If there is no such ability to intercept the binding between the attribute and its delegate, in order to achieve the same function, you must explicitly pass the attribute name, which is not very convenient

// 检查属性名称而不使用“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)
   // 创建委托
}

In the generated code, the provideDelegate method is called to initialize the auxiliary prop$delegate property. Compare the code generated for the property declaration val prop: Type by MyDelegate() with the code generated above (when the provideDelegate method does not exist)

class C {
    var prop: Type by MyDelegate()
}

// 这段代码是当“provideDelegate”功能可用时
// 由编译器生成的代码:
class C {
    // 调用“provideDelegate”来创建额外的“delegate”属性
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)
}

Please note that the provideDelegate method only affects the creation of auxiliary properties, and does not affect the code generated for getters or setters

 

Guess you like

Origin blog.csdn.net/PrisonJoker/article/details/113874122