前言
提起委托我们很容易想到委托模式,本质上就是将操作委托给另一方去执行。在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类名>()
进行初始化,简单方便。以上内容旨在做个分享,如有错误的地方欢迎指正。