美文网首页Kotlin
Kotlin 入门(四):委托

Kotlin 入门(四):委托

作者: 孤独的根号十二 | 来源:发表于2019-02-19 14:48 被阅读19次

    委托

    类委托

    类 Derived 可以继承一个接口 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(args:  Array<String>)  {   
    val b   =   BaseImpl(10)            
    Derived(b).print()  
    //  输出  10 
    }
    

    Derived 的超类型列表中的 by -子句表示 b 将会在 Derived 中内部存储。 并且编译器将 生成转发给 b 的所有 Base 的方法

    委托属性

    有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够为大 家把他们只实现一次并放入一个库会更好。例如包括
    1.延迟属性(lazy properties): 其值只在首次访问时计算,
    2.可观察属性(observable properties): 监听器会收到有关此属性变更的通知,
    3.把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
    为了涵盖这些(以及其他)情况,Kotlin 支持 委托属性:

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

    语法是: val/var <属性名>: <类型> by <表达式> 。在 by 后面的表达式是该 委托, 因为属性 对应的 get() (和 set() )会被委托给它的 getValue() 和 setValue() 方法。 属性的委 托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue() ——对于 var 属性)。 例如:

    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.'")                } 
    }
    
    

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

    val e   =   Example() 
    println(e.p)
    
    输出结果:
    Example@33a17727,   thank   you for delegating  ‘p’ to  me!
    
    

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

    e.p =   "NEW"
    输出结果:
    NEW has been    assigned    to  ‘p’ in  Example@33a17727.
    
    

    标准委托

    Kotlin 标准库为几种有用的委托提供了工厂方法。
    1.延迟属性Lazy
    lazy() 是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延 迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

    val lazyValue:  String  by  lazy    {
    println("computed!")
    "Hello"
     }
    
    fun main(args:Array<String>){
    println(lazyValue)          
    println(lazyValue) 
    }
    
    这个例子输出:
    computed!
    Hello 
    Hello
    
    

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

    1. 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(args:  Array<String>)  {
    val user    =   User()
    user.name   =   "first"
    user.name   =   "second" 
    }
    
    输出:
    <no name>   ->  first 
    first   ->  second
    
    

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

    把属性储存在映射中

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

    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
    
    

    局部委托属性(自 1.1 起)

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

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

    属性委托要求

    1.对于一个只读属性(即 val 声明的),委托必须提供一个名为 getValue 的函数,该函数接 受以下参数:
    (1)thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者 是它的超类型
    (2)property —— 必须是类型 KProperty<*> 或其超类型
    这个函数必须返回与属性相同的类型(或其子类型)。
    2.对于一个可变属性(即 var 声明的),委托必须额外提供一个名为 setValue 的函数,该函 数接受以下参数:
    (1)thisRef —— 同 getValue()
    (2)property —— 同 getValue()
    (3)new value —— 必须和属性同类型或者是它的超类型

    委托类可以实现包含所需 operator 方法的 ReadOnlyProperty 或 ReadWriteProperty 接口之 一。 这俩接口是在 Kotlin 标准库中声明的:

    interface   ReadOnlyProperty<in R,  out T>  {
    operator    fun getValue(thisRef:   R,  property:   KProperty<*>):  T }
    interface   ReadWriteProperty<in    R,  T>  {
    operator    fun getValue(thisRef:   R,  property:   KProperty<*>):  T               operator    fun setValue(thisRef:   R,  property:   KProperty<*>,   value:  T) }
    
    

    委托属性的原理

    在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属 性 prop ,生成隐藏属性 prop$delegate ,而访问器的代码只是简单地委托给这个附加属 性:

    class C {
    var prop:   Type    by  MyDelegate() 
    }
    //  这段是由编译器生成的相应代码:
     class  C   {
    private val prop$delegate   =   MyDelegate()
    var prop:   Type
    get()=prop$delegate.getValue(this,  this::prop)
    set(value: Type)=prop$delegate.setValue(this,   this::prop, value)
     }
    
    

    Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。

    provideDelegate

    通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右 侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来 创建属性 委托实例。
    provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中) 检查属性一致性。
    例如,如果要在绑定之前检查属性名称,可以这样写:

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

    在创建 MyUI实例期间,为每个属性调用 provideDelegate方法,并立即执行必要的验证。 如果没有这种拦截属性与其委托之间的绑定的能力,为了实现相同的功能, 你必须显式传递 属性名,这不是很方便:

    //  检查属性名称而不使用“provideDelegate”功能
     class MyUI {
    val image   by  bindResource(ResourceID.image_id,"image")
    val text    by  bindResource(ResourceID.text_id,    "text")
     }
    fun <T> MyUI.bindResource(id:ResourceID<T>,propertyName:String ):
    ReadOnlyProperty<MyUI,T>{
    checkProperty(this,propertyName)
    //  创建委托 
    }
    

    在生成的代码中,会调用 provideDelegate 方法来初始化辅助的 prop$delegate 属性。 比较 对于属性声明 val prop: Type by MyDelegate() 生成的代码与 上面(当 provideDelegate 方 法不存在时)生成的代码:

    class   C   {
    var prop:   Type    by  MyDelegate()
     }
    //  这段代码是当“provideDelegate”功能可用时 // 由编译器生成的代码:
     class  C   {               
    //  调用“provideDelegate”来创建额外的“delegate”属性
    private val prop$delegate   =MyDelegate().provideDelegate(this,this::prop)
    val prop:Type get() =
    prop$delegate.getValue(this,    this::prop) 
    }
    
    

    相关文章

      网友评论

        本文标题:Kotlin 入门(四):委托

        本文链接:https://www.haomeiwen.com/subject/tpwxyqtx.html