Kotlin 委托

什么是委托?

把自己的事情托付给别人来办理。

常见的代理模式就是如此,被代理类接受代理类的委托来处理代理类的事情。

这里不谈设计模式,只记录一下 kotlin 中的 类委托和属性委托。

  • 属性委托:把属性的 setter 和 getter 委托给另一个类处理
  • 类委托:把自己事情委托给另一个类处理

为什么需要委托?

属性委托可以使用另一个类的属性来替换当前属性的 getter 和 setter 。

类委托可以使用组合代替继承。

kotlin 在语法层面对委托进行了支持,从而减少了很多的样板代码

用在什么场景下?

属性委托

在你想把属性的读取逻辑放在另一个地方的时候就可以用到了,比如你想更改一个属性名字,而这个库已经发出去了,删除是不可能的,只能废弃,这时候就可以使用委托,将这个旧属性委托给新属性。

还有 标准库里提供的几个场景

  1. 延迟初始化;属性在第一次使用时才会初始化,而延迟逻辑就放在了类的外面,委托给了另一个类处理
  2. 可观察属性;可以观察属性的改变,一旦有变化可以得到通知,这些观察的逻辑实现也委托到了另外的类处理
  3. 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。

类委托

委托模式已经证明是实现继承的一个很好的替代方式。

怎么使用?

kotlin 在语法层面支持了委托,编译器会帮助我们生成样板代码。

委托属性

语法:

val/var <属性名>: <类型> by <表达式>。by 后面的表达式就是委托。
class Example {
    var p: String by Delegate()
}

标准库里的 属性延迟初始化委托和可观察委托使用

val s:String by lazy {
    "Hello s."
}
val name :String by Delegates.observable("佛系编码"){property,oldValue,newValue ->
    println("${property.name} 的值即将发生变化,新值:${newValue},旧值:${newValue}")
}

标准库中能提供了 延迟属性委托的工厂方法 lazy ,传入一个初始化值的 lambda 就能使用了。

只有在没有被赋值时才会被调用一次并将 lambda的返回值赋值存储,成功赋值后就会直接使用值了,不会再调用 lambda 表达式。如果出现赋值错误下次会再次调用直到成功赋值为止。

lazy 还有一个参数是可选的 mode ,

val s:String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    "Hello s."
}

该参数接受一个枚举类型 LazyThreadSafetyMode,目前有三个枚举值

  • LazyThreadSafetyMode.SYNCHRONIZED 锁定并确定只有一个线程可初始化 Lazy 实例;默认值。
  • LazyThreadSafetyMode.PUBLICATION 初始化函数可以调用多次,但只有第一次返回的值被使用。
  • LazyThreadSafetyMode.NONE 无锁

标准库里除了 lazy 委托,在 Delegates 类里还有很多其他委托,就不一一的列举了,详情可以自己看看源码或者 《Kotlin编程实践》 第八章。

类委托

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}

如何自定义属性委托

只需要委托类提供 getValue 和 setValue (对应 var 变量)函数 即可,不需要实现任何接口。

标准的函数签名如下

import kotlin.reflect.KProperty

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

标准库中也提供了接口 ReadOnlyProperty 和 ReadWriteProperty 分别对应 val 和 var ,觉得函数签名不好记得话,直接实现这个接口就好了。

示例

class Example{
    var p :String by MyDelegate()
    val s:String by lazy {
        "Hello s."
    }

}
class MyDelegate:ReadWriteProperty<Example,String>{
    override fun setValue(thisRef: Example, property: KProperty<*>, value: String) {
        println("MyDelegate 的 setValue 被调用 thisRef:$thisRef , property:${property.name} , value:$value")
    }

    override fun getValue(thisRef: Example, property: KProperty<*>): String {
        return "MyDelegate 的 getValue 被调用 thisRef :$thisRef,property:${property.name}"
    }

}

kotlin 的委托如何实现的?

kotlin 中的委托是由编译器将代码生成了,编译为 Java 代码就能看到。

属性委托,是将本来被委托的属性的 getter 和 setter 交由委托类的 getValue 和 setValue 处理了。
而类委托更简单了,内部完全是调用了委托的实现。

直接上代码更加直观一些,将上面的示例编译为 Java 代码可看到如下代码

被委托属性 p 只有 setter 和 getter 方法了。本身这个属性就不存在于类成员里了。

学习资料

  • 委托属性
  • 《Kotlin 编程实践》
  • 《Kotlin 核心编程》
posted @ 2021-01-17 20:59  佛系编码  阅读(335)  评论(0编辑  收藏  举报