委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。
一、类委托
类委托的核心思想在于将一个类的具体实现委托给另一个类去完成。在前面的章节中,我们曾经使用过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
}