用一个属性代理另一个对象的属性

2020-02-20 13:35:07 浏览数 (3)

最近好忙呀,上周因为上上周末加班都没顾上写文章。 好在昨天活动见到了膜拜已久的冰冰,很好很强大。

今天来分享给大家一个属性代理的例子。总是有人问我属性代理有什么用,这个也许可以为你提供些思路。

话说我们经常会有这样的需求场景:

代码语言: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,所以我们在使用时就可以把文章开头的代码改写为:

代码语言:javascript复制
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 添加一个副构造器如下:

代码语言:javascript复制
...
    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 就可以进一步简化:

代码语言:javascript复制
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 的代理类:

代码语言:javascript复制
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 的代码:

代码语言:javascript复制
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'

最后再说一句,估计雀雀又要吐槽我了,这也是没有办法的事儿,哈哈。就这样。


0 人点赞