委托:一种设计模式,一种能够替代继承的方式。操作对象自己不去处理某段逻辑而是委托给另一个对象去处理。
一.类委托
将一个子类的具体实现委托给另一个子类去完成。这句话的意思就是说一个子类必须实现的接口属性、方法可以交给另一个已经实现了该接口的子类去完成。实现一个List接口:
class MyList<T>: List<T> {
override val size: Int
get() = TODO("Not yet implemented")
override fun contains(element: T): Boolean {
TODO("Not yet implemented")
}
override fun containsAll(elements: Collection<T>): Boolean {
TODO("Not yet implemented")
}
override fun get(index: Int): T {
TODO("Not yet implemented")
}
override fun indexOf(element: T): Int {
TODO("Not yet implemented")
}
.
.
.
}
可以看到实现List接口就需要重写大量的抽象方法,这是无法避免的。现在我们让MyList的构造方法接收一个ArrayList对象:
class MyList<T>(val arrayList: ArrayList<T>): List<T> {
override val size: Int
get() = arrayList.size
override fun contains(element: T): Boolean {
return arrayList.contains(element)
}
override fun containsAll(elements: Collection<T>): Boolean {
return arrayList.containsAll(elements)
}
override fun get(index: Int): T {
return arrayList[index]
}
override fun indexOf(element: T): Int {
return arrayList.indexOf(element)
}
override fun isEmpty(): Boolean {
return arrayList.isEmpty()
}
...
}
可以发现MyList的所有实现父类接口的方法都可以由ArrayList对象来完成,这在一定程度上就是委托,但是很显然这种实现并没有给予我们代码或者逻辑上任何的便利。所以kotlin基于这点提供了java所没有的by关键字用来完成委托的任务:
class MyList<T>(private val arrayList: ArrayList<T>): List<T> by arrayList {
}
注意:类委托只针对接口的实现,即委托的类(MyList)必须实现一个接口类(List),如果MyList继承一个抽象类或者一个非抽象子类是无法完成托管的
可以看到通过关键字by将任务全部都委托给了arrayList去完成,MyList中不需要写任何代码就可以实现List接口。但是为什么不直接继承ArrayList?直接继承从代码量和逻辑结果上看和委托是一样的。最开始我们说过kotlin弄个by关键字就是为了替代继承,就是为了让你不要去继承ArrayList,那为什么不让你去继承?因为kotlin中所有实现类默认都是final(通常这个关键字会省略)的,他无法被继承,如果想要被继承就需要用open关键字修饰:
class A<T>: B<T>() {
}
open class B<T>(): C<T> {
override fun c() {
TODO("Not yet implemented")
}
}
interface C<T> {
fun c()
}
这样写是OK的,但是虽然B被open修饰了,A可以继承B,但是B类里面非继承的属性和方法在A类中仍然无法重写,这时就需要用open关键字继续修饰B的成员属性和方法:
class A<T>: B<T>() {
override val bb = 2
override fun b() {
}
}
open class B<T>(): C<T> {
open val bb = 1
open fun b() {
}
override fun c() {
TODO("Not yet implemented")
}
}
interface C<T> {
fun c()
}
使用委托:
class A<T>(val bbb: B<T>): C<T> by bbb{
fun a() {
bbb.bb++
bbb.b()
}
}
class B<T>(): C<T> {
var bb = 1
fun b() {
}
override fun c() {
TODO("Not yet implemented")
}
}
interface C<T> {
fun c()
}
B不用被open就可以访问其内部方法和属性,但是这里也有局限性就是A无法重写B的属性或方法,因为A归根结底实现的还是C接口,他委托给B的目的就是为了让B替他重写C接口里面的方法,至于不能重写B就无关紧要。
二.属性委托
将一个属性的具体实现委托给另一个类去完成。成员属性最重要的两个功能就是赋值和取值即set和get方法,所以属性委托的根本就是将这两个方法的功能委托出去:
class MyList<T>(private val arrayList: ArrayList<T>): List<T> by arrayList {
private val my: String by My()
}
class My {
var value = ""
operator fun <T> getValue(myList: MyList<T>, property: KProperty<*>): String {
return value
}
operator fun <T> setValue(myList: MyList<T>, property: KProperty<*>, v: String) {
value = v
}
}
但是这样委托出去了有什么用呢,这个String类型的my变量我本来就可以直接取值赋值。我们再看一个例子:
class MyList<T>(private val arrayList: ArrayList<T>): List<T> by arrayList {
private var my: Person by My()
}
class My {
var value = Person("a", 1)
operator fun <T> getValue(myList: MyList<T>, property: KProperty<*>): Person {
return value
}
operator fun <T> setValue(myList: MyList<T>, property: KProperty<*>, v: Person) {
value = v
}
}
这里my作为一个自定义的Person类对象,他需要被初始化,同样的他在其他类中也是需要被初始化赋值的,而这个逻辑是可以通用的,所以我们可以通过委托将初始化逻辑委托给一个公共的类My去完成,这样就可以大大简化代码。最典型的例子就是kotlin为我们提供了两个Fragment的扩展方法:activityViewModels()和viewModels(),他俩创建的viewModel对象分别对应activity和fragment的生命周期,通过by viewModels()和by activityViewModels()我们就可以把viewModel的初始化逻辑委托出去:
另外提一下getValue和setValue这两个方法:他们需要被operator关键字修饰,另外他们的第一个参数表示这个委托类的委托功能可以在什么类及其子类中使用,所以这里的MyList<T>替换为List<T>也是可以的,即List接口的所有实现类都可以使用这个My类来完成委托功能;第二个参数KProperty<*>是kotlin的一个属性操作类,用来获取各种属性相关的值,目前没有什么用但必须声明,<*>在kotlin中的作用就等同于?在java中的作用,表示不需要或者不关心泛型的具体类型。
三.标准委托
kotlin标准库中已经为我们提供了一些委托方法,包括我们最常用的lazy。
1.lazy
lazy是一个接收方法参数的顶层方法,当第一次执行getValue的时候这个方法体会被调用,所以这个属性可以被延迟初始化,之后的调用都只会返回同一个值。这是非常有趣的特性, 当我们在它们第一次真正调用之前不是必须需要它们的时候,我们可以节省内存,在这些属性真正需要前不进行初始化。
示例:
database在调用writableDatabase方法之前就会被lazy方法体里面的最后一行代码赋值即MyDatabaseHelper(applicationContext),且只会赋值一次。来模拟下lazy顶层方法的实现:
fun <T> later(b: () -> T) = Later(b)
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any?, prop: KProperty<*>): T {
if (value == null) {
value = block()
}
return value as T
}
}
class Person(val name: String, val age: Int) {
val p: String by later {
Log.e("tag", "!!!")
"123"
}
init {
val a = p
val b = p
}
}
//!!!
!!!只会打印一遍
只有当value为空时才会去执行block方法对象即lazy后的方法体,所以可以看到!!!只会打印一遍
2.Observable
这个委托会监测我们希望观察的属性的变化。当被观察属性的set方法被调用的时候,它就会自动执行我们指定的lambda表达式。 所以一旦该属性被赋了新的值,我们就会接收到被委托的属性、旧值和新值。
class Data(val db: MyDatabase) {
var my by observable(0) {//这里的0是默认值,第一次赋值时old就等于0
d, old, new ->
db.saveChanges(this, new)//每次值被修改了,就会保存它们到数据库
}
}
3.Vetoable
这是一个特殊的observable,它决定新的值是否真的要被赋予,它返回一个布尔型变量:
class Data(val db: MyDatabase) {
var my by vetoable(0) { d, old, new ->
if (new > 0) {
new > old
} else {
false
}
}
}
//在这个委托逻辑中,调用set方法时会对新的值new进行判断,只有当new>0且大于原值时才会被真正赋值
总的来说在kotlin中委托模式就是把若干个类或者若干个属性的某一公共代码部分委托出去,统一实现统一管理