kotlin 委托

2022-11-18

委托

主流语言都有委托模式.有些语言只能单父类继承,拓展性不强,不够灵活,使用委托替代,能够更灵活的控制实现的粒度.

kotlin 通过关键字 by 委托给具体实现

interface Base {
  val message: String
	fun print()
}
class BaseImpl(val x: Int) : Base {
  override val message = "BaseImpl: x = $x"
	override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
	val b = BaseImpl(10)
	Derived(b).print()
}

由委托实现的接口成员可以被覆盖,下例输出为’abc’.

class Derived(b: Base) : Base by b{
  override fun print() { print("abc") }
}
fun main() {
	val b = BaseImpl(10)
	Derived(b).print()
}

但是如果涉及到成员的重写,则不会被委托对象的成员调用:

class BaseImpl(val x: Int) : Base {
  override val message = "BaseImpl: x = $x"
	override fun print() { print(message) }
}
class Derived(b: Base) : Base by b{
  override val message = "Derived message"
}
fun main() {
	val b = BaseImpl(10)
	Derived(b).print()
}
//out:BaseImpl: x = 10

属性委托

语法: val/var <属性名>:类型 by <表达式>.属性对应的get()和set()方法会委托给表达式的```getValue```和```getValue```方法.

class Delegate {
    var delegateValue:String = "123"
    operator fun getValue(thisRef:Any?,property:KProperty<*>):String{
        return "$thisRef, thank you for delegating '${property.name}' to me! and value is $delegateValue"
    }
    operator fun setValue(thisRef: Any?,property: KProperty<*>,value: String){
        println("$value has been assigned to '${property.name}' in $thisRef.")
        delegateValue = value
    }
}
class Derived(){
    var p:String by Delegate()
}
fun main() {
    val driverd = Derived()
    println(driverd.p)
    driverd.p = "abc"
    println(driverd.p)
}
//out:
//Derived@77459877, thank you for delegating 'p' to me! and value is 123
//abc has been assigned to 'p' in @77459877.
//@77459877, thank you for delegating 'p' to me! and value is abc

当从委托到一个Delegate实例的p读取时,将调用DelegategetValue()函数,它的第一个参数是读出p的对象,第二个参数保存了对p自身的描述.

延迟属性 Lazy properties

内置lazy()是接受一个lambda并返回一个lazy<T>实例的函数,返回的实例可以作为实现延迟属性的委托.第一次调用get()会执行已传递给lazy()lambda表达式并记录结果.后续调用只是返回记录的结果.默认为线程安全的实现.

class Derived(){
    val lazyValue:String by lazy {
        println("computed!")
        "hello"
    }
}
//source code
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
internal object UNINITIALIZED_VALUE
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
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this
    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            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
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."
    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

可以看到_value被声明为@Volatil保证了可见性以及原子性.关于属性的volatile说明,java内存模型中线程会存储本地内存副本(local memory),它会缓存JVM共享变量.声明为volatile的属性在写时会插入内存屏障 StoreStore;writeValue;StoreLoad;,在读时会插入内存屏障 LoadLoad;readValue;LoadStore;,这些内存屏障可以防止指令重排.我们可以看到单纯的读和写操作具有原子性,并且当其它线程读取该变量时会先同步共享变量到本地内存,即当一个线程修改属性值后其它线程读取时都能立刻读取到修改后的值,该属性的修改对于多个线程来说是可见的.

可观察属性 Observable properties

方法Delegates.observable()接受两个参数:初始值与修改时处理程序(handler). 每当我们给属性赋值时会调用该处理程序(在赋值后执行).它有三个参数:被赋值的属性、旧值与新值:

import kotlin.properties.Delegates
class User {
	var name: String by Delegates.observable("<no name>") {prop, old, new ->
		println("$old -> $new")
	}
}
fun main() {
	val user = User()
	user.name = "first"
	user.name = "second"
}

委托给另一个属性

⼀个属性可以把它的 getter 与 setter 委托给另一个属性。这种委托对于顶层和类的属性 (成员和扩展)都可用。该委托属性可以为:

顶层属性

同⼀个类的成员或扩展属性

另⼀个类的成员或扩展属性

为将一个属性委托给另一个属性,应在委托名称中使用::限定符,例如,this::delegateMyClass::delegate.

var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)
class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
	var delegatedToMember: Int by this::memberInt
	var delegatedToTopLevel: Int by ::topLevelInt
	val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt

将属性存储在映射中

一个常见的用例是在一个映射(map)中存储属性的值.这经常出现在像解析JSON或者执行其他“动态”任务的应用中,在这种情况下你可以使用映射实例自身作为委托来实现委托属性.

class User(val map: Map<String, Any?>) {
	val name: String by map
	val age: Int by map
}

局部委托属性

你可以将局部变量声明为委托属性。 例如,你可以使⼀个局部变量惰性初始化:

fun example(computeFoo: () -> Foo) {
	val memoizedFoo by lazy(computeFoo)
	if (someCondition && memoizedFoo.isValid()) {
		memoizedFoo.doSomething()
	}
}

变量memoizedFoo只会在第一次访问时计算,如果someCondition失败,那么该变量根本不会计算.

属性委托要求

对于一个只读属性(即val声明的),委托必须提供一个操作符函数getValue(),该函数具有以下参数:

thisRef必须与属性所有者类型(对于扩展属性必须是被扩展的类型)相同或者是其超类型.

property必须是类型Property<*>或其超类型.

getValue必须返回与属性相同的类型(或其子类型).

class Resource
class Owner {
	val valResource: Resource by ResourceDelegate()
}
class ResourceDelegate {
	operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
		return Resource()
	}
}

对于一个可变属性(即var声明的),委托必须额外提供一个操作符函数setValue(),该函数具有以下参数:

thisRef必须与属性所有者类型(对于扩展属性必须是被扩展的类型)相同或者是其超类型.

property必须是类型Property<*>或其超类型.

value必须与属性类型相同(或者是其超类型).

class Resource
class Owner {
	var varResource: Resource by ResourceDelegate()
}
class ResourceDelegate(private var resource: Resource = Resource()) {
	operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
		return resource
	}
	operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) {
		if (value is Resource) {
			resource = value
		}
	}
}

提供委托

通过定义provideDelegate操作符,可以扩展创建属性实现所委托对象的逻辑.如果by右侧所使用的对象将provideDelegate定义为成员或扩展函数,那么会调用该函数来创建属性委托实例.

One of the possible use cases of provideDelegateis to check the consistency of the property upon its initialization.

例如,如需在绑定之前检测属性名称,可以这样写:

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
	override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}
class ResourceLoader<T>(id: ResourceID<T>) {
	operator fun provideDelegate(thisRef: MyUI,prop: KProperty<*>): ReadOnlyProperty<MyUI, T> {
		checkProperty(thisRef, prop.name)
		//创建委托
		return ResourceDelegate()
	}
	private fun checkProperty(thisRef: MyUI, name: String) { …… }
}
class MyUI {
	fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… }
	val image by bindResource(ResourceID.image_id)
	val text by bindResource(ResourceID.text_id)
}

provideDelegate的参数与getValue的相同:

thisRef必须与属性所有者类型(对于扩展属性必须是被扩展的类型)相同或者是其超类型.

property必须是类型Property<*>或其超类型.

在创建MyUI实例期间,为每个属性调用provideDelegate方法,并立即执行必要的验证.