Kotlin系列八----委托

概述

  • 类委托

委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它。这是官方对委托的描述,可能比较抽象但当你了解其意义和使用,会发现委托所带来的方便会让你爱不释手,下面我们来开始学习委托吧,首先看一个官方的例子:

interface Base {
    fun print()
}

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

class Derived(b: Base) : Base by b

上述Derived类,继承了接口Base,理应实现接口Base中的方法,但此处不仅没有实现反而出现了 by b ,因为Derived的类的主构造函数传入的对象本身是实现了Base接口的,这里的by 代表 b 将会在 Derived 中内部存储, 编译器将生成转发给 b 的所有 Base 的方法。这句话更直观的理解是创建的Derived的对象所执行的方法,都委托给b,相当于调用对象b的方法一样(针对实现Base接口的方法才会委托)。

Derived 和 BaseImpl类中添加一个方法,看看此时会调用哪个方法:

class BaseImpl(val x: Int) : Base {
    override fun print() { Log.e("================",x.toString()) }

    fun log(){
        Log.e("================","BaseImpl===============")
    }
}

class Derived(b: Base) : Base by b{
    fun log(){
    Log.e("================","Derived===============")
}
}

创建Derived 的对象,并分别调用print()和log()方法,运行结果:

02-28 02:20:40.062 4226-4226/com.example.administrator.kotlinpractise E/================: 10
02-28 02:20:40.062 4226-4226/com.example.administrator.kotlinpractise E/================: Derived===============

此处可以看到print方法中国输出的是BaseImpl中的方法,而log输出的是Derived的方法,现在知道上面最后括号中那句话的意识乐吧。

  • 委托属性:
委托的对象必须提供 getValue(和setValue)方法,因为在对被委托的对象调用时,系统会自动调用委托对象的get(),在被委托负值时,会调用他的set方法
class  Example {
    var e : String by Delege()
}

class Delege{
    operator fun getValue(thisRef: Any?, property: KProperty<*>) : String {
        Log.e("getValue============","getValue")
        return "$thisRef"
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String){
        Log.e("setValue============","$value")
    }

现在创建并负值Example的对象:

var example = Example()
Log.e("=========", example.e)
example.e = "New Example"
Log.e("=========", example.e)

现在来看看打印的日志:

02-28 03:02:04.826 5876-5876/com.example.administrator.kotlinpractise E/getValue============: getValue
02-28 03:02:04.826 5876-5876/com.example.administrator.kotlinpractise E/=========: com.example.administrator.kotlinpractise.Example@3d2e49e
02-28 03:02:04.827 5876-5876/com.example.administrator.kotlinpractise E/setValue============: New Example
02-28 03:02:04.827 5876-5876/com.example.administrator.kotlinpractise E/getValue============: getValue
02-28 03:02:04.827 5876-5876/com.example.administrator.kotlinpractise E/=========: com.example.administrator.kotlinpractise.Example@3d2e49e
对于一个只读属性(即 val 声明的),委托必须提供一个名为 getValue 的函数,该函数接受以下参数:
thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;
property —— 必须是类型 KProperty<*> 或其超类型。
这个函数必须返回与属性相同的类型(或其子类型)。
对于一个可变属性(即 var 声明的),委托必须额外提供一个名为 setValue 的函数,该函数接受以下参数:
thisRef —— 同 getValue();
property —— 同 getValue();
new value —— 必须和属性同类型或者是它的超类型。

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

  • 可观察属性 Observable

接受两个参数:初始值和修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值和新值,在每次为变量赋值的时候委托的程序会执行

var observer : String by Delegates.observable("A"){
    property,old,new ->
    Log.e("$old=====","$new")
}
Log.e("=========",observer)
observer = "test"
Log.e("=========",observer)

输出结果:

02-28 02:51:36.356 5496-5496/com.example.administrator.kotlinpractise E/=========: A
02-28 02:51:36.356 5496-5496/com.example.administrator.kotlinpractise E/A=====: test
02-28 02:51:36.356 5496-5496/com.example.administrator.kotlinpractise E/=========: test

  • vetoable()
在属性被赋新值生效 之前 会调用传递给   vetoable   的处理程序,你可以根据条件选择接受或拒绝赋值,参数:初始值和判断程序( 要求返回一个布尔值 ),当返回为true赋值成功,false则拒绝赋值
var observer: String by Delegates.vetoable("A") { property, old, new ->
     new.equals("NEW")
}
Log.e("=========", observer)
observer = "test"
Log.e("=========", observer)
observer = "NEW"
Log.e("=========", observer)

输出结果:

02-28 03:02:04.827 5876-5876/com.example.administrator.kotlinpractise E/=========: A
02-28 03:02:04.827 5876-5876/com.example.administrator.kotlinpractise E/=========: A 
02-28 03:02:04.827 5876-5876/com.example.administrator.kotlinpractise E/=========: NEW

  • 把属性储存在映射中

一个常见的用例是在一个映射(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
这也适用于   var   属性,如果把只读的   Map   换成   MutableMap   的话:
class MutableUser ( val map : MutableMap < String , Any ?> ) {
var name : String by map
var age : Int by map
}


到这里大家对委托应该有了初步的了解,个人感觉委托在开发中还是很实用的,比如一些需要需要读取和设置的数据,可以使用属性委托免去一些代码且逻辑性更好,之后还会继续出一遍关于委托更深入的应用,今天暂时到这吧。

猜你喜欢

转载自blog.csdn.net/alexwll/article/details/79604628