前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用一个属性代理另一个对象的属性

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

作者头像
bennyhuo
发布2020-02-20 13:35:07
8340
发布2020-02-20 13:35:07
举报
文章被收录于专栏:BennyhuoBennyhuo

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

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

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

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

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


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kotlin 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档