您的位置:首页 > 其它

Kotlin学习之-5.13 代理属性

2017-07-11 20:14 169 查看

Kotlin学习之-5.13 代理属性

有一些常见的属性,尽管我们可以每次需要他们的时候手动实现他们,但是最好还是可以实现一次然后随处可用,并把它放到库里。 这些例子包括:

懒加载属性:属性值会在第一次访问的时候被计算

可观察的属性:监听器会得到属性变化的通知

存储在map中的属性,而不是单独给每个属性一个单独的变量

为了覆盖这些情况,Kotlin支持代理属性

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


语法是:
val/var <property name>: <Type> by <expression>
。 在
by
之后的表达式就是代理,因为属性对应的
get()
set()
方法会被代理成它的
getValue()
setValue()
方法。属性代理不是必须实现接口,但是他们必须提供
getValue()
setValue()
方法。 例如

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


荡我们读取
p
的时候,他会代理到一个
Delegate
对象,然后
Delegate
中的
getValue()
函数会被调用,因此对象的第一个参数是我们要读取的对象
p
,第二个参数持有
p
本身的描述。 例如:

val e = Example()
println(e.p)


这会输出

Example@33a17727, thank you for delegating 'p' to me!


类似的,当我们给
p
赋值的时候,
setValue()
方法会被调用。前两个参数和签名一样,第三个参数持有要被赋值的值:

e.p = "NEW"


这会输出

NEW has been assigned to 'p' in Example@33a17727


代理对象的具体要求在下面会详细描述。

注意从Kotlin v1.1 开始,可以在一个函数或者代码块中声明一个代理属性,它不是必须是一个类的成员。

标准代理

Kotlin标准库提供一些有用的代理工厂方法。

懒函数

lazy()
函数接收一个lambda表达式并且返回一个
Lazy<T>
的实例,这个实例可以当做一个实现了懒属性的代理:第一次调用
get()
时执行传递给
lazy()
函数的lambda表达式并记住它的结果,后续的调用
get()
就直接返回记住的结果。

val lazyValue: String by lazy {
println("computed!")
"Hello"
}

fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}


这个例子会输出

computed!
Hello
Hello


默认情况下,懒属性的评估是同步的(synchronized):只会在一个线程中计算它的值,并且所有线程看到的值是相同的。如果不要求代理初始化的同步,那么多个线程都可以同时执行它,传递
LazyThreadSafetyMode.PUBLICATION
当做
lazy()
函数的一个参数。并且,如果你确认它的初始化总是在一个线程上执行,你可以使用
LazyThreadSafeMode.NONE
模式,这样就不会有任何线程安全的保障以及相应的开销。

可观察属性(Observable)

Delegates.observalbe()
接收两个参数:初始值和一个对于修改的回调。每次当我们给属性赋值的时候,赋值完成后,回调都会被调用。 它有三个参数:被赋值的属性,属性的原值,属性的新值。

import kotlin.properties.Delegates

class User {
var name: String by Deleates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}

fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}


这个例子会输出

<no name> -> first
first -> second


如果你想要能够拦截一个赋值并改写它,使用
vetoable
来替代
observable()
。这样传递给
vetoble
的回调是在赋值之前调用。

在Map中存储属性

一种常见的情况是在Map中存储属性。当解析JSON或者处理其他动态的事情时经常需要这么做,你可以使用map对象本身作为一个代理属性的代理。

class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}


在这个例子中,构造器接收一个map:

val user = User(mapOf(
"name" to "John Dow",
"age"  to 25
))


代理属性从map中接收值,通过属性的键值对

println(user.name) // 输出"John Doe"
println(user.age) // 输出25


对于
var
属性也可以使用
MutableMap


class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}


局部代理属性(从Kotlin v1.1)

你可以什么局部变量作为代理属性。例如,你可以让一个局部变量延迟加载:

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

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

}


变量
memorizedFoo
的值仅会在第一次被访问时计算。如果
someConditon
的结果是‘否’,那么变量值永远都不会被计算。

属性代理的要求

这里我们总结一下代理对象的要求。

对于一个只读属性(例如
val
),一个代理必须提供一个名为
getValue
的函数,这个函数接收下面两个参数:

thisRef
, 必须和属性拥有者是一样的,或者是它的父类。

property
,必须是
KProperty<*>
类型或者它的父类。

这个函数返回值的类型必须和属性的类型是一样的,或者是它的子类。

对于可变属性(
var
),一个代理必须附加提供一个名为
setValue
的函数,这个函数接收下面三个参数:

thisRef
getValue()
中的一样

property
getValue()
中的一样

new value, 类型必须和属性的类型一样活着是它的父类。

getValue()
setValue()
函数可以被用作代理类的成员函数或者扩展函数。后者非常帮助,当你想要代理一个对象,但是它并没有提供这些方法。这两个函数都需要用关键字
operator
来标记。

代理类可以实现两个接口中的任意一个,
ReadOnlyProperty
ReadWriteProperty
,他们都包含必须的
operator
操作符函数。 这些接口在Kotlin标准库中已经定义。

interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, out t> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}


翻译规则

在Kotlin中每个代理的背后是编译器生成了一个附加的属性并且代理到这个属性上。例如,对于属性
prop
,对应生成隐藏的属性是
prop$delegate
,并且访问者的代码就是肩带代理到这个附加的属性。

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


Kotlin编译器给
prop
属性在参数中提供所有可能需要的信息:第一个参数
this
引用一个外部类
C
的实例,
this::prop
是一个
KProperty
类型的反射对象,可以表示
prop
自己。

注意
this::prop
这样的语法参考bound callable reference仅在Kotlin v1.1之后可用.

提供一个代理(Kotlin v1.1之后支持)

通过定义
provideDelegate
操作符,你可以扩展创建对象的逻辑,这些对象属性的实现是代理的。如果对象用在
by
的右手边,定义
provideDelegate
当做一个成员或者扩展函数,这个函数可以被调用来创建属性代理对象。

一个可能的
provideDelegate
用例是在创建属性的时候检查属性一致性,而不仅仅是在他的getter 和setter方法。

例如,如果你想要在绑定之前检查属性名,你可以这样写:

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


provideDelegate
的参数是和
getValue
的参数是一样的:

thisRef
, 类型必须和属性所有者的类型是一样的或者是他的父类

property
,类型必须是
KProperty<*>
或者是它的父类

provideDelegate
方法在
MyUI
实例创建的时候会为每个属性调用,并且他会立即执行必要的验证。

如果没有在属性和他的代理之间拦截绑定的能力,想要实现同样的功能你必须显式地传递属性名,这样不是很方便。

// 不使用'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)
}


在生成的代码中,
provideDelegate
方法会被调用来初始化附加的
prop$delegate
属性。 比较属性声明的代码,
val prop: Type by MyDelegate()


class C {
val 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)
}


注意
provideDelegate
方法仅影响附加属性的创建并且不会影响生成代码的getter 和setter

PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  kotlin 代理模式