Kotlin 委托和委托属性

1、委托

在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

1.1. 委托实现

Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base
Derived 的超类型列表中的 by-子句表示 b 将会在 Derived 中内部存储, 并且编译器将生成转发给 b 的所有 Base 的方法。

interface Base {
    
    
    fun print()
}

class BaseImpl(val x: Int) : Base {
    
    
    override fun print() {
    
     print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    
    
    val b = BaseImpl(10)
    Derived(b).print()
}

1.2 覆盖委托的接口成员

interface Base {
    
    
    fun printMessage()
    fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
    
    
    override fun printMessage() {
    
     print(x) }
    override fun printMessageLine() {
    
     println(x) }
}

class Derived(b: Base) : Base by b {
    
    
    override fun printMessage() {
    
     print("abc") }
}

fun main() {
    
    
    val b = BaseImpl(10)
    //如果当前类 覆盖了接口的成员,就用覆盖的方法
    Derived(b).printMessage()
    //如果没有覆盖接口的方法,就使用接口的实现类
    Derived(b).printMessageLine()
}

重写接口的属性

interface Base {
    
    
    val message: String
    fun print()
}

class BaseImpl(val x: Int) : Base {
    
    
    override val message = "BaseImpl: x = $x"
    override fun print() {
    
     println(message) }
}

class Derived(b: Base) : Base by b {
    
    
    // 在 b 的 `print` 实现中不会访问到这个属性
    override val message = "Message of Derived"
}
fun main() {
    
    
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print()
    println(derived.message)
}

2、委托属性

语法是: val/var <属性名>: <类型> by <表达式>
在 by 后面的表达式是该 委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法

class Example {
    
    
    var p: String by Delegate()
}


class Delegate {
    
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    
    
        return "$thisRef, thank you for delegating '${
      
      property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    
    
        println("$value has been assigned to '${
      
      property.name}' in $thisRef.")
    }
}

result:
Example@33a17727, thank you for delegating ‘p’ to me!

当我们从委托到一个 Delegate 实例的 p 读取时,将调用 Delegate 中的 getValue() 函数, 所以它
第一个参数是读出 p 的对象、
第二个参数保存了对 p 自身的描述 (例如你可以取它的名字)

类似地,当我们给 p 赋值时,将调用 setValue() 函数。前两个参数相同,第三个参数保存将要被赋予的值:

e.p = "NEW"

result:
NEW has been assigned to ‘p’ in Example@33a17727.

2.1 延迟属性(lazy properties):

其值只在首次访问时计算;
lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托。
第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

//执行一次,下次就会保存到委托对象里。
val lazyValue: String by lazy {
    
    
    println("computed!")
    "Hello"
}

fun main() {
    
    
    println(lazyValue)
    println(lazyValue)
}

对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。
而如果你确定初始化将总是发生在与属性使用位于相同的线程, 那么可以使用 LazyThreadSafetyMode.NONE 模式:它不会有任何线程安全的保证以及相关的开销。

val lazyValue: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
    
    
  ....
}

2.2 可观察属性(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"
}

如果你想截获赋值并“否决”它们,那么使用 vetoable() 取代 observable()。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序。

2.3 委托给另一个属性

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

  • 顶层属性
  • 同一个类的成员或扩展属性
  • 另一个类的成员或扩展属性
    为将一个属性委托给另一个属性,应在委托名称中使用恰当的 :: 限定符,例如,this::delegate 或 MyClass::delegate。
var topLevelInt: Int = 0

class ClassWithDelegate(val anotherClassInt: Int)

class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
    
    
    //委托本类成员加  this::
    var delegatedToMember: Int by this::memberInt
    //委托文件内成员加::
    var delegatedToTopLevel: Int by ::topLevelInt
    //委托其他类成员加  类::成员
    val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
//这是扩展属性
var MyClass.extDelegated: Int by ::topLevelInt

这是很有用的,例如,当想要以一种向后兼容的方式重命名一个属性的时候:引入一个新的属性、 使用 @Deprecated 注解来注解旧的属性、并委托其实现。

class MyClass {
    
    
   var newName: Int = 0
   @Deprecated("Use 'newName' instead", ReplaceWith("newName"))
   var oldName: Int by this::newName
}

fun main() {
    
    
   val myClass = MyClass()
   // 通知:'oldName: Int' is deprecated.
   // Use 'newName' instead
   myClass.oldName = 42
   println(myClass.newName) // 42
}

2.4 把多个属性储存在一个映射(map)中,

而不是每个存在单独的字段中。
这也适用于 var 属性,如果把只读的 Map 换成 MutableMap

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

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

println(user.name) // Prints "John Doe"
println(user.age)  // Prints 25

2.5 局部委托属性

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

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

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

2.6 属性委托要求

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

thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是其超类型。
property —— 必须是类型 KProperty<*> 或其超类型。
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 —— 必须是类型 KProperty<*> 或其超类型。
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
        }
    }
}

getValue() 或/与 setValue() 函数可以通过委托类的成员函数提供或者由扩展函数提供。 当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。 两函数都需要用 operator 关键字来进行标记。

2.7 ReadWriteProperty

也可以自定义ReadWriteProperty 的匿名实现类,重写getValue() setValue()方法。
getValue() is declared in ReadOnlyProperty;
ReadWriteProperty extends it and adds setValue()

fun resourceDelegate(): ReadWriteProperty<Any?, Int> =
    object : ReadWriteProperty<Any?, Int> {
    
    
        var curValue = 0 
        override fun getValue(thisRef: Any?, property: KProperty<*>): Int = curValue
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
    
    
            curValue = value
        }
    }

val readOnly: Int by resourceDelegate()  // ReadWriteProperty as val
var readWrite: Int by resourceDelegate()

2.8 委托原理

在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它

class xx{
    
    
    var prop: MyDelegate by MyDelegate()
}
class MyDelegate{
    
    
    operator fun getValue(xx: xx, property: KProperty<*>): MyDelegate {
    
    
        return MyDelegate()
    }
    operator fun setValue(xx: xx, property: KProperty<*>, myDelegate: MyDelegate) {
    
    
    }
}

经过编译后,为下面结构
为xx类,创建了 new MyDelegate(); ,然后执行getValue() setValue()方法。

public final class xx {
    
    
   // $FF: synthetic field
   static final KProperty[] $$delegatedProperties = new KProperty[]{
    
    (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(xx.class, "prop", "getProp()Lcom/example/kotlin_sample/objclass/MyDelegate;", 0))};
   @NotNull
   private final MyDelegate prop$delegate = new MyDelegate();

   @NotNull
   public final MyDelegate getProp() {
    
    
      return this.prop$delegate.getValue(this, $$delegatedProperties[0]);
   }

   public final void setProp(@NotNull MyDelegate var1) {
    
    
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.prop$delegate.setValue(this, $$delegatedProperties[0], var1);
   }
}

提供委托???

猜你喜欢

转载自blog.csdn.net/chentaishan/article/details/120953608