委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。
一、类委托
类委托的核心思想在于将一个类的具体实现委托给另一个类去完成。在前面的章节中,我们曾经使用过 Set 这种数据结构,它和 List 有点类似,只是它所存储的数据是无序的,并且不能存储重复的数据。Set 是一个接口,如果要使用它的话,需要使用它的具体的实现类,比如 HashSet。而借助于委托模式,我们可以轻松实现一个自己的实现类。比如定义一个 MySet,并让它实现 Set 接口,代码如下所示:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
override val size: Int
get() = helperSet.size
override fun contains(element: T) = helperSet.contains(element)
override fun containsAll(elements: Collection<T>) =
helperSet.containsAll(elements)
override fun isEmpty() = helperSet.isEmpty()
override fun iterator() = helperSet.iterator()
}
可以看到,MySet 构造函数中接收了一个 HashSet 参数,相当于一个辅助对象,然后在 Set 接口所有的方法实现中,我们都没有进行自己的实现,而是调用了辅助对象中相应的方法实现,这其实就是一种委托模式。
我们如果让大部分的方法实现调用辅助对象中的方法,少量部分的方法实现由自己来重写,甚至加入一些自己独有的方法, 那么 MySet 就会成为一个全新的数据结构类,这就是委托模式的意义所在。
但是目前这种写法也有一定弊端,如果接口中待实现方法有几十个甚至上百个,每个都去这样调用辅助对象中的方法的话就会非常麻烦,所以我们就需要使用委托的功能来解决。
Kotlin 中委托的关键字是 by
,只需要在接口声明后面使用 by
关键字,再加上委托的辅助对象,就可以免去之前所写的一大堆板式的代码了。如下所示:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
// 这段代码和上段代码实现的效果是一模一样的
// 如果我们要对某个方法重新实现,只需要单独重写那一个方法即可
// 下面代码重写了isEmpty方法以及新增helloWorld方法
fun helloWorld() = println("Hello World")
override fun isEmpty() = false
}
二、属性委托
委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。下面是委托属性的语法结构:
class MyClass {
var p by Delegate()
}
这里使用 by
关键字将 p 属性的具体实现委托给了 Delegate 类去完成,当调用 p 属性的时候会自动调用 Delegate 类的 getValue() 方法,当给 p 属性赋值的时候会自动调用 Delegate 类的 setValue() 方法,下面是 Delegate 类的具体实现:
class Delegate {
var propValue: Any? = null
operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
return propValue
}
operator fun setValue(myClass: MyClass, prop: KProperty<*>,
valueL Any?) {
propValue = value
}
}
这是一种标准的代码实现模板,在 Delegate 类中我们必须实现 getValue() 和 setValue() 这两个方法,并且都要使用 operator 关键字进行声明。
getValue() 接收两个参数,第一个参数用于声明该 Delegate 类的委托功能可以在什么类中使用,这里写成 MyClass 表示仅可在 MyClass 类中使用,第二个参数 KProperty<*>
是 Kotlin 中的一个属性操作类,另外 <*>
这种写法表示不知道或者不关心泛型的具体类型,只是为了通过语法编译,类似于 Java 中的 <?>
写法,返回值可以声明成任何类型,根据具体实现逻辑写就可以。
三、实现自己的 lazy 函数
by lazy{}
代码块中的代码在一开始并不会执行,只有当变量被首次调用的时候,代码块中的代码才会被执行。实际上 by lazy 并不是连在一起的关键字,只有 by 才是关键字,lazy 只是一个高阶函数而已,在 lazy 函数中会创建并返回一个 Delegate 对象,当我们调用 p 属性的时候,其实调用的是 Delegate 对象的 getValue() 方法,然后 getValue() 方法中又会调用 lazy 函数传入的 Lambda 表达式,这样表达式中的代码就可以得到执行了,并且调用 p 属性后得到的值就是 Lambda表达式最后一行代码的返回值。
class Later<T>(val block: () -> T) {
val value: Any? = null
operator fun getValue(any: Any?, prop: KProperty<*>): T {
if(value == null) {
value = block()
}
return value as T
}
}
// 为了使它的用法更类似于lazy函数,再定义一个顶层函数
fun <T> later(block: () -> T) = Later(block)
// 替换之前的lazy代码
val uriMatcher by later {
val matcher = UriMatcher(UriMatcher.NO_MATCH)
...
matcher
}