Kotlin学习(十五)——委托

委托模式已经证明是实现继承的⼀个很好的替代方式式,而 Kotlin 可以零样板代码地原生支持它。类 Derived 可以继承⼀个接口Base ,并将其所有共有的方法委托给⼀个指定的对象 这相当于Java的动态代理模式

类委托

interface Base{
    fun print()
}
class BaseImp1 :Base{
    override fun print() {
        println("abc" )
    }
}
//把所有的方法实现委托给一个BB接口的子类
//实际上BaseSon1继承Base接口,本来要实现当中的抽象方法,但是把方法实现委托给传入的Base的初始化参数b(Base的子类对象)
class BaseSon1(b:Base) : Base by b

//这种情况下,如果我们使用overrride覆盖了我们委托要实现的方法,那么就会调用覆盖的方法,而不是委托的方法实现
//我们继承了接口,又实现了接口的抽象方法,这时候是是本类的覆盖方法优先
class BaseSon2(b:Base) : Base by b{
    override fun print() {
        println(10)
    }
}
fun main(args: Array<String>) {
    val baseImp = BaseImp1()
    BaseSon1(baseImp).print() //输出abc,因为已经把实现委托给baseImp了
    BaseSon2(baseImp).print() //输出10,因为BaseSon2实现了抽象方法
}

BaseSon1的超类型列表中的 by-⼦句表⽰ b 将会在 BaseSon1 中内部存储。并且编译器将⽣成转发给 b 的所有 Base 的⽅法。

注意:覆盖会以你所期望的方式⼯作:编译器会使⽤你的 override 实现取代委托对象中的实现。如上面代码我们为BaseSon2 添加 override fun print() { print(“abc”) } ,该程序会输出“abc”⽽不是“10”

属性委托

有⼀些常⻅的属性类型,虽然我们可以在每次需要的时候⼿动实现它们,但是如果能够为⼤家把他们只实现⼀次并放⼊⼀个库会更好。例如包括
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 Example {
    var p : String by Delete()
}
//被委托的类要提供属性对应的getValue()和setValue()
class Delete{
    operator fun getValue(example: Example, property: KProperty<*>): String {
        return "$example, thank you for delegating '${property.name}' to me!"
    }
    //example代表了Example的实例对象,property代表了类的相应的属性 s是相应属性的值
    operator fun setValue(example: Example, property: KProperty<*>, s: String) {
        println("$s has been assigned to '${property.name} in $example.'")
    }
}
fun main(args: Array<String>) {
    var e = Example()
    e.p = "hello"
    println(e.p)
}

标准委托

Kotlin 标准库为⼏种有⽤的委托提供了⼯⼚⽅法。
lazy() 是接受⼀个 lambda 并返回⼀个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托:第⼀次调⽤ get() 会执⾏已传递给lazy() 的 lambda 表达式并记录结果,后续调⽤ get() 只是返回记录的结果。

class BB{
    //只有val类型的变量能延迟初始化,而且只初始化一次
    //lazy是一个lambda表达式,是无参的返回的值就是“hello”
    val s : String by lazy {
        println("set property")
        "hello"
    }
    fun foo(){
        //执行下面的代码,只调用一次延迟代码块,所以只有一次的输出
        println(s)
        println(s)
    }
}
fun main(args: Array<String>) {
    BB().foo() //值输出一次set property
}

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

可观察属性 Observable

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

class BB{
    //首先要观察属性,要赋给属性一个初始的值,lambda表达式的三个参数是:属性,老的值,新的值
   var s:String by Delegates.observable("no name"){
        pro,old,new ->
        println("$old--->$new  and $pro")
    }
    //如果你想能够截获⼀个赋值并“否决”它,就使⽤ vetoable() 取代 observable() 。
    // 在属性被赋新值⽣效之前会调⽤传递给 vetoable 的处理程序。
    //下面的就是在设置值前判断新值和旧值的长度,如果长度小于旧的值就被否决
    var i: String by Delegates.vetoable("he"){
        pro, old, new ->
        new.length > old.length
    }
}
fun main(args: Array<String>) {
    var b = BB()
    println(b.i)
    b.i= "hello"
    println(b.i)
    b.i = "kl"
    println(b.i)
}

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

把属性储存在映射中

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

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

    override fun toString(): String {
        return "$name is $age years old"
    }
}
fun main(args: Array<String>) {
    val user = User(mapOf("name" to  "marry","age" to 11))
    println(user)

}

局部委托属性(⾃ 1.1 起)

class Foo{
    fun isValid():Boolean{
        return true
    }
    fun doSomething(){
        println("初始化成功了")
    }
}
//方法内也可以使用延迟化属性,通过lambda表达式来传如一个类型
fun example(computeFoo: () -> Foo){
    val memoizedFoo by lazy(computeFoo)
    val someCondition : Boolean by lazy {
        true
    }
    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }

}
fun main(args: Array<String>) {
    example { Foo() }
}

属性委托要求

这⾥我们总结了委托对象的要求。
对于⼀个只读属性(即 val 声明的),委托必须提供⼀个名为 getValue 的函数,该函数接受以下参数:
1. thisRef ⸺ 必须与 属性所有者 类型(对于扩展属性⸺指被扩展的类型)相同或者是它的超类型,
2. property ⸺ 必须是类型 KProperty<*> 或其超类型,
3. 这个函数必须返回与属性相同的类型(或其⼦类型)。

interface Exam{
}
class Example:Exam{
    val name :String by Delegate()
}

class Delegate{
    /*
    example : 就是所说的thisRef。它的类型必须与 属性所有者类型(对于扩展属性⸺指被扩展的类型)相同或者是它的超类型
               也就是说example可以是Example类型,也可以是他的超类Exam类型。
    property :必须是类型 KProperty<*> 或其超类型
    返回值:与属性相同的类型(或其⼦类型)
     */
    operator fun getValue(example: Example, property: KProperty<*>): String {
        return "$example is get ${property.name}"
    }
}
fun main(args: Array<String>) {
    println(Example().name)
}

对于⼀个可变属性(即 var 声明的),委托必须额外提供⼀个名为 setValue 的函数,该函数接受以下参数:
1. thisRef ⸺ 同 getValue() ,
2. property ⸺ 同 getValue() ,
3. new value ⸺ 必须和属性同类型或者是它的超类型。

class Example {
    var p : String by Delete()
}
//被委托的类要提供属性对应的getValue()和setValue()
class Delete{
    operator fun getValue(example: Example, property: KProperty<*>): String {
        return "$example, thank you for delegating '${property.name}' to me!"
    }
    /*
    example : 就是所说的thisRef。它的类型必须与 属性所有者类型(对于扩展属性⸺指被扩展的类型)相同或者是它的超类型
              也就是说example可以是Example类型,也可以是他的超类Exam类型。
    property :必须是类型 KProperty<*> 或其超类型
    s:就是所谓的newValue,类型必须和属性同类型或者是它的超类型
     */
    operator fun setValue(example: Example, property: KProperty<*>, s: String) {
        println("$s has been assigned to '${property.name} in $example.'")
    }
}
fun main(args: Array<String>) {
    var e = Example()
    e.p = "hello"
    println(e.p)
}

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

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

翻译规则

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

class C {
var prop: Type by MyDelegate()
} 
// 这段是由编译器⽣成的相应代码:
class C {
    //编译器会为属性prop生成如下的私有变量。
    private val prop$delegate = MyDelegate()
    //下面是编译器生成的setter和getter,调用的是上面私有属性的getValue()方法和setValue()方法。
    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 自身。
请注意,直接在代码中引⽤绑定的可调⽤引⽤的语法 this::prop ⾃ Kotlin 1.1 起才可⽤

提供委托

通过定义 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)
}

小编是真的看不懂这个代码,所以,等待以后理解在发博客,先把官方文档的代码贴上

provideDelegate 的参数与 getValue 相同:
thisRef ⸺ 必须与 属性所有者 类型(对于扩展属性⸺指被扩展的类型)相同或者是它的超类型,property ⸺ 必须是类型 KProperty<*> 或其超类型。
在创建 MyUI 实例期间,为每个属性调⽤ 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)
}

请注意,provideDelegate ⽅法只影响辅助属性的创建,并不会影响为 getter 或 setter ⽣成的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值