最近好忙呀,上周因为上上周末加班都没顾上写文章。 好在昨天活动见到了膜拜已久的冰冰,很好很强大。
今天来分享给大家一个属性代理的例子。总是有人问我属性代理有什么用,这个也许可以为你提供些思路。
话说我们经常会有这样的需求场景:
代码语言:javascript复制class Wrapper {
private val wrapped: Wrapped = Wrapped(false)
var x : Boolean
get() = wrapped.x
set(value){
wrapped.x = value
}
var y: Int = 0
set(value){
wrapped.setY(value)
field = value
}
val z: Long
get() = wrapped.z
}
我们用一个类的属性来代理内部对象的属性,这样做的目的当然是希望内部的 target
不被暴露,同时部分的 api 也可以让外部获得访问权限。
可是这些代码看起来总觉得,应该可以更简单一些,比如用个属性代理什么的。
代码语言:javascript复制class ObjectPropertyDelegate<T, R>(val target: R, val getter: ((R) -> T)? = null, val setter: ((R, T) -> Unit)? = null, defaultValue: T? = null) {
private var value: T? = defaultValue
operator fun getValue(ref: Any, property: KProperty<*>): T {
return getter?.invoke(target)?:value!!
}
operator fun setValue(ref: Any, property: KProperty<*>, value: T) {
setter?.invoke(target, value)
this.value = value
}
}
当然这个 ObjectPropertyDelegate
使用的是没有绑定 receiver
的 getter 和 setter,所以我们在使用时就可以把文章开头的代码改写为:
class Wrapper {
private val wrapped: Wrapped = Wrapped(false)
var x by ObjectPropertyDelegate(wrapped, Wrapped::x, Wrapped::x::set) // getter 处也可使用 Wrapped::x::get
var y by ObjectPropertyDelegate(wrapped, setter = Wrapped::setY, defaultValue = 0)
val z by ObjectPropertyDelegate(wrapped, Wrapped::z) // getter 处也可使用 Wrapped::z::get
}
对于 x
,似乎我们还可以简化一下,毕竟它是一个属性,通过一个属性我们似乎就可以把它的 setter 直接拿到,而不需要显式的传入了。
我们为我们的 ObjectPropertyDelegate
添加一个副构造器如下:
...
constructor(target: R, property: KProperty1<R, T>, defaultValue: T? = null)
:this(target, property, if(property is KMutableProperty1<*, *>) (property as KMutableProperty1<R, T>)::set else null, defaultValue)
...
那么我们的 Wrapper
就可以进一步简化:
class Wrapper {
private val wrapped: Wrapped = Wrapped(false)
var x by ObjectPropertyDelegate(wrapped, Wrapped::x)
var y by ObjectPropertyDelegate(wrapped, setter = Wrapped::setY, defaultValue = 0)
val z by ObjectPropertyDelegate(wrapped, Wrapped::z)
}
但你以为这就是巅峰状态了吗?Naive。既然是 ObjectDelegate,我们不免就要想,为什么不能用绑定了 receiver 的属性或者函数引用作为参数呢?
于是乎,我们定义了另外一个 ObjectPropertyDelegate0
的代理类:
class ObjectPropertyDelegate0<T>(val getter: (() -> T)? = null, val setter: ((T) -> Unit)? = null, defaultValue: T? = null) {
constructor(propertyRef: PropertyReference, defaultValue: T? = null)
:this((propertyRef as KProperty0<T>)::get, if(propertyRef is KMutableProperty0<*>) (propertyRef as KMutableProperty0<T>)::set else null, defaultValue)
private var value: T? = defaultValue
operator fun getValue(ref: Any, property: KProperty<*>): T {
return getter?.invoke()?:value!!
}
operator fun setValue(ref: Any, property: KProperty<*>, value: T) {
setter?.invoke(value)
this.value = value
}
}
那么我们进一步简化 Wrapper
的代码:
class Wrapper {
private val wrapped: Wrapped = Wrapped(false)
var x by ObjectPropertyDelegate0(wrapped::x)
var y by ObjectPropertyDelegate0(setter = wrapped::setY, defaultValue = 0)
val z by ObjectPropertyDelegate0(wrapped::z)
}
简单说下,这个 ObjectPropertyDelegate0
的主构造器仍然是留给直接传入函数引用的情况,例如 wrapped::setY
,副构造器则用于支持直接传入属性,例如 wrapped::x
。
如果你对 Kotlin 的属性或者函数引用类型的命名比较熟悉的话,你应该知道前面的
ObjectPropertyDelegate
这时候就应该被命名为ObjectPropertyDelegate1
了。
你以为这就完了吗?怎么会。前面的代码看上去还是不够直接,不够简洁,不如我们为属性和函数定义一个扩展吧:
代码语言:javascript复制fun <T> KProperty0<T>.delegator(defaultValue: T? = null) = ObjectPropertyDelegate0(propertyRef = this as PropertyReference, defaultValue = defaultValue)
fun <T, R> KProperty1<R, T>.delegator(receiver: R, defaultValue: T? = null) = ObjectPropertyDelegate1(receiver, property = this, defaultValue = defaultValue)
fun <T> KFunction1<T, Unit>.delegator(defaultValue: T? = null) = ObjectPropertyDelegate0(setter = this, defaultValue = defaultValue)
fun <T, R> KFunction2<R, T, Unit>.delegator(receiver: R, defaultValue: T? = null) = ObjectPropertyDelegate1(receiver,setter = this, defaultValue = defaultValue)
于是乎,最终的版本就是这样:
代码语言:javascript复制class Wrapper {
private val wrapped: Wrapped = Wrapped(false)
var x by wrapped::x.delegator()
var y by wrapped::setY.delegator(defaultValue = 0)
val z by wrapped::z.delegator()
}
今天的例子基本上到这儿接近尾声了,不过我再提一句,这个例子需要引入的包是这样的:
代码语言:javascript复制import kotlin.jvm.internal.PropertyReference
import kotlin.reflect.*
如果对于反射以及属性及函数引用这样的概念和知识点不是很了解,那么这篇文章可能会看起来比较迷。。
本文所涉及的代码已经托管在 GitHub:https://github.com/enbandari/ObjectPropertyDelegate,并发布在 jcenter 上。
大家如果有兴趣,也可以在 gradle 中引入:
代码语言:javascript复制compile 'com.bennyhuo.kotlin:opd:1.0-rc'
最后再说一句,估计雀雀又要吐槽我了,这也是没有办法的事儿,哈哈。就这样。