Kotlin Reference (十四) 委托类和委托属性

491 阅读3分钟
原文链接: blog.csdn.net

KotLin 相关文档


官方在线Reference
kotlin-docs.pdf
Kotlin for android Developers 中文翻译
Kotlin开发工具集成,相关平台支持指南
Kotlin开源项目与Libraries
Kotlin开源项目、资源、书籍及课程搜索平台
Google’s sample projects written in Kotlin
Kotlin and Android

 

Kotlin委托机制


委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。

 

委托类 (Class Delegation)


例,

interface Base {
    fun print()
}

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

class Derived(b: Base) : Base by b {
    fun add() {

    }
}

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // prints 10
    Derived(b).add()
}

在基类列表上使用by子句,表示,Derived类的内部对象b,会生成Base的所有公共函数。这时调用Derived的属于Base的公共函数,就会委托调用b对象的对应函数。

Derived是一个委托类,其对应的委托实例就是构造函数中Base的实例b  

委托属性 (Delegated Properties)


有一些常见的属性,虽然我们可以每次需要时手动实现它们,但现在只需要如下形式的使用,即可实现。这些属性,如:

  • lazy properties 只在第一次访问时进行值计算

  • observable properties 监听获取关于属性变化的通知

  • storing properties
    将属性存储在一个Map中
     

委托属性语法

例,

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.'")
    }
}

class Example {
    var p: String by Delegate()
}

fun main(args: Array<String>) {
    val example = Example()
    example.p = "stone"
    println(example.p)
}

输出:

stone has been assigned to 'p in com.stone.clazzobj.delegation.Example@238e0d81.'
com.stone.clazzobj.delegation.Example@238e0d81, thank you for delegating 'p' to me!

委托属性语法:
val/var <property name>: <Type> by <expression>
如,上例中的Example的属性p

这里将属性p的get/set委托给Delegate类的getValue/setValue函数来处理。

属性委托类,如Delegate类,其可以在形式上继承/实现一个基类/接口(这里指非特定的),然而该接口的函数在外部并不能由委托属性调用,所以继承/实现的特性,在这里并没有什么大的意义  

标准委托属性

Kotlin标准库,为如下几种有用的委托形式,提供了相应的工厂方法

  • lazy
val lazyValue: String by lazy {
    println("computed!") //多次调用  只会输出一次
    "Hello"
}

查看lazy源码,发现它调用的是:

public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

SynchronizedLazyImpl这是一个同步执行的函数,内部实现是加了synchronized的同步代码块的;所以在多线程环境中,第一个进入同步代码块执行出的结果,就是lazy-property的值。

再看lazy的一个重载方法:

public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
        when (mode) {
            LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
            LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
            LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
        }

使用,如

val lazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {...}

LazyThreadSafetyMode对应的三种模式: a. SYNCHRONIZED 同步

b. PUBLICATION 发布。内部的计算过程可能被多次访问,但内部的属性是一个Volatile修饰的属性,所以在多线程环境中,被第一次访问获取数据后,此后的其它线程都共享该值。

c. NONE
未对多线程环境,做任何处理。
所以在多线程环境中使用,会造成:计算和返回值都可能处在多个线程中。

注:lazy属性,只能声明为 val的,即它是只能get的

 

  • Observable
    需要在属性后跟by Delegates.observable() ,即调用这个方法设定属性值。该方法是两个参数:初始值,lambda表达式(即处理值的语句块)。
    例,
import kotlin.properties.Delegates
class User {
    var name: String by Delegates.observable("<no name>") {
        property, oldValue, newValue ->
        println("$oldValue -> $newValue") }
    }
fun main(args: Array<String>) { 
    val user = User() 
    user.name = "first" 
    user.name = "second"
}

输出:

<no name> -> first
first -> second

Delegates.observable()中,使用的lambda表达式,其参数:
I. property:在这指属性name
II. oldValue: 旧值,首次即为指定的初始值
III. newValue:新set的值
最终属性值=new值

还有个Delegates.vetoable(),跟Delegates.observable()类似,只是lambda表达式的返回值为Boolean。
返回true:属性值=new值
返回false:属性值=old值

observable 属性,可以定为val的,不过既然要监听属性的变化,还是用var较好

 

  • Storing Properties in a Map
    将属性委托给(存进)一个Map。
    例,
class Per(val map: Map<String, Any?>) { 
    val name: String by map
    val age: Int by map
}

fun main(args: Array<String>) { 
    val per = Per(mapOf("name" to "John Doe", "age" to 25))
    println(per.name)
    println(per.age)
}

输出:

John Doe
25

也可以使用一个var map,即map的类型为MutableMap

 

局部委托属性

在kotlin1.1后,可以定义局部的委托属性。如,

class Foo {
    fun isValid(): Boolean {
        return Random().nextBoolean()
    }

    fun doSomething() {
        println("doSomething")
    }
}
fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo) //memoizedFoo: Foo
    if (memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
//  var p: String by Delegate() //可以有其它形式的委托属性
}

fun main(args: Array<String>) {
    example {
        println("生成Foo")
        Foo()
    }
}

上例的example(),接受一个函数参数,该函数返回Foo类型对象。
memoizedFoo,就是一个局部委托属性;这里委托的是public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
这个函数,该lazy函数接受一个函数参数。
局部委托属性也可以是其它的委托属性形式。
 

委托属性给一个类实例之总结

  • 对于一个val修饰的只读属性

    它的委托必须提供一个名为getValue的函数。该函数中的两个参数的意义,如下
    thisRef : 其类型,必须是委托属性的所有者或其super类型
    property : 必须是KProperty<*>或其super类型 KCallable<*>

  • 对于一个var修饰的可变属性

    其含有参数,除了上述两个外,还多一个new-value值

注意getValue/setValue函数的声明以operator开始 这两个函数,可以直接定义成委托类的成员函数,或委托类的扩展函数。后者对于在原始类中未提供它们时,提供了方便

  • ReadOnlyProperty 和 ReadWriteProperty
    属性的委托类,可以实现两个接口,kotlin.properties.ReadOnlyPropertykotlin.properties.ReadWriteProperty;这两个接口中,声明了getValue(var和val属性)/setValue(var属性)函数。

    例,

class Example1: Super() {
    val p1: String by Delegate1()
}

class Delegate1: ReadOnlyProperty<Example1, String> {
    override fun getValue(thisRef: Example1, property: KProperty<*>): String {
        return ""
    }
}

不实现这两个接口也是可以的,如之前使用 operator声明的getValue/setValue函数

 

委托属性转化规则

声明的委托属性,会由编译器产生一个隐藏属性,且会生成对应的get/set方法。

如,

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

 

provideDelegate函数提供委托实例

在kotlin1.1,提供了provideDelegate函数,它可以为属性提供对象委托逻辑。
如果 by 右侧所使用的对象将 provideDelegate( 函数名不能随意变更) 定义为成员或扩展函数,那么会调用该函数来 创建属性委托实例;且要求委托实例的类,要实现 ReadOnlyProperty 或 ReadWriteProperty接口

例,

class Dele(val id: String): ReadOnlyProperty<MyUI, String> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): String {
        return "${UUID.randomUUID()}_$id"
    }
}
class ResourceID {
    private constructor(id: String) {
        this.id = id
    }
    open val id: String
    companion object {
        val image_id = ResourceID("1")
        val text_id: ResourceID = ResourceID("2")
    }

}
class ResourceLoader(resId: ResourceID) {
    private val resourceID = resId

    operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, String> {
        checkProperty(thisRef, prop.name)
        // create delegate
        return  Dele(resourceID.id+"")
    }
    private fun checkProperty(thisRef: MyUI, name: String) {
        if (name.equals("image")) {
            //...
        }
    }
}
fun MyUI.bindResource(id: ResourceID): ResourceLoader {
    return ResourceLoader(id)
}
class MyUI {
    val image by bindResource(ResourceID.image_id) //bindResource()产生委托对象
    val text by bindResource(ResourceID.text_id)
}
fun main(args: Array<String>) {
    println("image-id = " + MyUI().image)
    println("txt-id = " + MyUI().text)
}

上例中,找到by关键字,其右侧调用了bindResource创建委托类实例。
bindResource(这是一个扩展函数)返回ResourceLoader类型的实例。
ResourceLoader中定义了一个operator声明的函数,名为provideDelegate,其返回真正的委托类Dele的实例对象。
Dele中,getValue函数,返回一个”拼接了UUID前缀+资源类型id”的字符串。

 

使用provideDelegate函数的委托属性转化规则

class C {
    var prop: Type by MyDelegate()
}
// this code is generated by the compiler
// when the 'provideDelegate' function is available: 
class C {
    // calling "provideDelegate" to create the additional "delegate" property
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop) 
    val prop: Type
}