关于Kotlin中委托那些事

前言

提起委托我们很容易想到委托模式,本质上就是将操作委托给另一方去执行。在Java中,委托只限制在类之间的委托实现,而在Kotlin中除了类委托外,还支持了属性委托,在辅以标准库提供的委托支持,可以让我们在应用中更加优雅的使用委托。

类委托

小菜是一名程序猿,他的工作是改bug,有一天小菜遇到了bug大魔王,以小菜的能力搞不定,于是小菜把搞定bug大魔王的工作委托给了同事小张,小张不愧是大佬,不费吹灰之力就搞定了bug大魔王。

上面小故事用代码表示的话,首先小张和小菜都有共同的工作–改bug,可以抽象出一个接口。

interface Programmer {
    fun fixBug()
}

小张作为被委托人,是搞定bug大魔王的功臣

class ProgrammerZhang : Programmer {
    override fun fixBug() {
        println("xiao zhang fix the bug")
    }
}

而小菜作为委托人,将解决bug大魔王的任务委托给小张

class ProgrammerCai(programmer: Programmer) : Programmer by programmer

//output --> xiao zhang fix the bug

这里参数传入的是接口,可以将上面故事改动一下,有三个同事都可以解决bug大魔王,不过小菜不知道谁有空,那么被委托对象就可能是三位中的某一个,不过他们都有一个共同点 – 都可以改bug(都实现 Programmer 接口)。这里传入接口参数就可以理解了。

Kotlin 中使用 by 关键字,来表示委托。上面小菜通过 by 关键字将解决bug大魔王工作委托给了小张。那么这里是类委托,尽管委托对象实现了Programmer接口,但是通过 by 关键字,最后会调用被委托人实现的fixBug()方法。

但是如果小菜同学突然灵感大发找到了解决思路,那也就不需要委托给小张了,自己就能解决。也就是说如果覆写了 fixBug() 方法 那么最后将不会调用被委托人的实现。

class ProgrammerCai(programmer: Programmer) : Programmer by programmer {
    override fun fixBug() {
        println("xiao cai fix the bug")
    }
}

//output --> xiao cai fix the bug

属性委托

虽然小菜工作很忙,但是还是坚持每周给头发做保养,但是保养的工作委托给了理发师托尼。

class ProgrammerCai{
   var hair : Hair by TonyDelegate() 
}

ProgrammerCai 中声明了 hair 属性,hair 通过 by 关键字委托给了 TonyDelegate()。这是属性委托的完整语法 val/var <属性名>: <类型> by <表达式> ,关键字 by后面就是委托 。要想正确的使用属性委托语法,委托需要实现委托属性的 getValue/setValue 方法,例如 TonyDelegate()中:

class TonyDelegate {

    private var newValue: Hair = Hair()

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Hair {
        println("Tony finished the maintenance.")
        return newValue
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Hair) {
        println("Give $value to Tony for maintenance.")
        value.isMaintain = true
        newValue = value
    }
}
  • thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。
  • property —— 必须是类型 KProperty<*> 或其超类型。
  • value — 必须与属性类型相同(或者是其超类型)。

如果自己要实现类似TonyDelegate的委托功能的时候记不住参数怎么办?没关系,Kotlin中也提供了两个接口ReadOnlyProperty/ReadWriteProperty分别对应 val/var修饰的属性。

class TomDelegate : ReadWriteProperty<ProgrammerCai,Hair>{
    override fun getValue(thisRef: ProgrammerCai, property: KProperty<*>): Hair {
       
    }

    override fun setValue(thisRef: ProgrammerCai, property: KProperty<*>, value: Hair) {
       
    }
}

如果有一天托尼聘请了一个助理,助理的工作就是帮托尼验证顾客信息,验证成功后再将顾客委托给托尼。这样该如何处理呢?

可以通过声明一个扩展方法来实现,这里传入的参数是属性名称。

private fun ProgrammerCai.assistantDelegate(name: String): ReadWriteProperty<ProgrammerCai, Hair> {
    checkData(name)
    return TonyDelegate()
}

class ProgrammerCai {
     var hair: Hair by assistantDelegate("hair")
 }

但是有一点不好的地方就是每次都要传入属性名称,有没有更简洁的处理方式?

有的,我们可以在这种情况下使用定义 provideDelegate 操作符来实现,修改下代码

class AssistantDelegate {
    operator fun provideDelegate(
        thisRef: ProgrammerCai,
        prop: KProperty<*>
    ): ReadWriteProperty<ProgrammerCai, Hair> {
        checkData(prop.name)
        return TonyDelegate()
    }
}

 class ProgrammerCai {
     var hair: Hair by AssistantDelegate()
 }

这样我们就不用每次都传入属性的名称了。

细究provideDelegate,它其实是在属性和其委托绑定之间做了拦截,我们可以根据具体场景具体应用。另外provideDelegate使用也有一定限制它的参数需要和getValue()参数保持一致。

标准库委托

Kotlin在委托方面的优化和封装可以说是极好的,目前Kotlin 标准库为几种有用的委托提供了工厂方法,它们分别是

  • 延迟属性(lazy properties): 其值只在首次访问时计算;
  • 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
  • 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
延迟属性(lazy properties)

延迟属性的值只能在首次访问的时候计算,它的标准使用是lazy方法, lazy接收一个 lambda 表达式返回一个Lazy对象。

val lazyValue:Int by lazy {
      0
}

使用起来很简单,如果继续深究一下lazy方法会发现它有两种声明方式

//方法1
public actual fun <T> lazy(initializer: () -> T): Lazy<T>

//方法2
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T>

方法2比方法1多了一个枚举参数mode可以指定实现的模式。

public enum class LazyThreadSafetyMode {
    SYNCHRONIZED,
    PUBLICATION,
    NONE,
}
  • SYNCHRONIZED — 使用锁保证线程安全
  • PUBLICATION — 初始化器函数可以在并发访问未初始化的[Lazy]实例值时被多次调用,但只有第一个返回值将被用作[Lazy]实例的值
  • NONE — 非线程安全,未处理任何并发,除非可以保证初始化不会被多个线程调用否则不建议使用

方法1尽管没有携带枚举参数,但是它使用的实现是线程安全模式下的实现。如果想继续看见lazy的庐山真面目,请继续往下走

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    //属性初始化块 
    private var initializer: (() -> T)? = initializer
    //具体属性值 
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    //对象锁
    private val lock = lock ?: this
    
    override val value: T
        get() {
            //如果已经初始化过,不再申请锁减少开销
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            //未初始化,使用synchronized保证线程同步
            return synchronized(lock) {
                val _v2 = _value
                //确保只初始化一次
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }
       ...
}

上面通过使用synchronized保证线程同步,两次判断保证只初始化一次,和双重校验锁实现的单例模式差不多。

有一种情况需要注意,如果使用了by lazy{}去惰性初始化属性,那么该属性不应该是var可变的。这一点可以通过返回值Lazy<T>可以看出一些端倪。

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

Lazy<T>只定义了getValue方法,所以不支持声明为var的属性使用。

可观察属性(observable properies)

Delegates.observale()是实现可观察属性赋值后监听的方法,相反的是有一个方法叫做Delegates.vetoable()是赋值前判断,满足条件才赋值。

class ProgrammerCai{
    var name:String by Delegates.observable("xiao cai"){
        property, oldValue, newValue ->
        //赋值完成后输出
        println("$oldValue->$newValue")
    }
  
    
    var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
            //如果 newValue大于30将不会赋值                                
            newValue < 30
    }
}

看起来不一样,其实它们的实现都是使用同一个类ObservableProperty,这个类实现了ReadWriteProperty接口,可以作为var可变属性的委托,内部额外声明了两个方法beforeChange()afterChange(),vetoable 和 observable 分别使用其中一个作为实现。

public override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
    val oldValue = this.value
    //vetoable使用的是beforChange()
    if (!beforeChange(property, oldValue, value)) {
        return
    }
    this.value = value
    //observable使用的是afterChange()
    afterChange(property, oldValue, value)
}
委托给映射对象
class ProgrammerCai(map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

使用起来也比较简单

val programmer = ProgrammerCai(
    mapOf(
        "name" to "xiao cai",
        "age" to 18
    )
)

从上面代码不难理解其原理就是依次从 map 中取 key 为 name 和 age 的值然后进行赋值。 原理是很简单的,但是在平时工作中却很少使用到,使用场景比较少,可能在Json解析的时候才会用到吧。另外如果将 map的类型改成 MutableMap可以支持可变属性。

总结

Kotlin中对委托做了很好的支持,我们可以在实际应用中灵活运用,比如 ViewModel 初始化可以在 Activity 中通过 by viewModels<ViewModel类名>()进行初始化,简单方便。以上内容旨在做个分享,如有错误的地方欢迎指正。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值