Kotlin 朱涛9 委托 代理 懒加载 Delegate

本文地址


目录

09 | 委托:你为何总是被低估?

Kotlin 的委托主要有两个应用场景,一个是委托类,另一个是委托属性

Jetpack Compose 中大量使用了 Kotlin 委托特性,理解委托是理解 Jetpack Compose 的前提。

核心语法:

  • 使用 var 修饰的属性,委托类中必须同时有使用关键字 operator 修饰的 getValuesetValue 方法
  • 方法 getValue、setValue 中的 thisRef 的类型,必须是被委托属性所属类的类型,或者其父类型
  • 方法 getValue 的返回值类型、setValue 的参数类型,必须是被委托属性的类型,或者其父类型

委托类

委托类常常用于实现类的委托模式

interface DB { fun save() }
class SqlDB : DB { override fun save() = println("save to sql") }
class GreenDaoDB : DB { override fun save() = println("save to GreenDao") }
class UniversalDB(db: DB) : DB by db // 通过关键字 by 将接口的实现,委托给了对象 db

fun main() {
    UniversalDB(SqlDB()).save()
    UniversalDB(GreenDaoDB()).save()
}

这种委托模式在我们的实际编程中十分常见,UniversalDB 相当于一个壳,它虽然实现了 DB 这个接口,但并不关心它怎么实现。具体是用 SQL 还是 GreenDao,传不同的委托对象进去,它就会有不同的行为。

以上委托类的写法,等价于以下 Java 代码:

class UniversalDB implements DB {
    private DB db;
    public UniversalDB(DB db) { this.db = db; }
    @Override public void save() { db.save(); } //  手动重写接口,将 save 委托给 db.save()
}

Kotlin 的委托类提供了语法层面的委托模式。通过这个 by 关键字,就可以自动将接口里的方法委托给一个对象,从而可以帮我们省略很多接口方法适配的模板代码。

委托属性

Kotlin 委托类委托的是接口方法,而委托属性委托的则是属性的 getter、setter

Kotlin 标准委托

Kotlin 提供了几种标准委托,包括:

属性间的直接委托

从 Kotlin 1.4 开始,可以直接在语法层面将属性 A 委托给属性 B

class Item {
    var count: Int = 0
    var total: Int by ::count // 把属性 total 的 getter/setter 委托给属性 count
}

fun main() {
    val item = Item()
    item.total = 1
    println("${item.total} - ${item.count}")
}

这里的::count属性的引用,它跟函数引用是一样的概念。

by lazy 懒加载委托

val data: String by lazy { // 实现属性的懒加载
    request()
}

fun request(): String {
    println("执行网络请求")
    return "网络数据"
}

fun main() {
    println(data)
    println("----------")
    println(data)
}
执行网络请求
网络数据
----------
网络数据
  • 未访问 data 时,request() 不会被触发执行
  • 第一次访问 data 时,request() 才会被触发执行
  • 后面再次访问 data 时,直接返回结果,request() 也不会被触发执行

懒加载委托其实是一个高阶函数:

// 函数 lazy 的参数是一个高阶函数,返回值类型是接口 Lazy<T>,实际返回的是实现类 SynchronizedLazyImpl
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

// 比上面的方法多了一个 enum 类型的参数
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)   // 多线程同步
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

手写自定义委托

通过自定义委托,可以根据自己的需求实现自己的属性委托。

import kotlin.reflect.KProperty

class StringDelegate(private var str: String) {
    operator fun getValue(thisRef: Owner, property: KProperty<*>): String = "$str - fromDelegate"
    operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
        println("setValue is called")
        str = "$value - setValue"
    }
}

class Owner {
    var text: String by StringDelegate("bqt") // 将 text 委托给 StringDelegate 的一个实例
}

fun main() {
    val owner = Owner()
    println(owner.text) // bqt - fromDelegate
    owner.text = "xxx"  // setValue is called
    println(owner.text) // xxx - setValue - fromDelegate
}
  • 使用 var 修饰的属性,委托类中必须同时有使用关键字 operator 修饰的 getValuesetValue 方法
  • 方法 getValue、setValue 中的 thisRef 的类型,必须是被委托属性所属类的类型,或者其父类型
  • 方法 getValue 的返回值类型、setValue 的参数类型,必须是被委托属性的类型,或者其父类型

接口自定义委托

如果觉得上面的写法太繁琐,也可以借助 Kotlin 提供的接口 ReadWritePropertyReadOnlyProperty,来自定义委托。通过实现接口,可以让 IntelliJ 帮我们自动生成 getValuesetValue 方法的声明。

  • 如果要为 val 属性自定义委托,就去实现 ReadOnlyProperty 接口
  • 如果要为 var 属性自定义委托,就去实现 ReadWriteProperty 接口
import kotlin.properties.ReadWriteProperty

class StringDelegate(private var str: String) : ReadWriteProperty<Owner, String> {
    override fun getValue(thisRef: Owner, property: KProperty<*>): String { // 省略了 operator,添加了 override
        TODO("Not yet implemented")
    }

    override fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
        TODO("Not yet implemented")
    }
}

provideDelegate 嵌套委托

使用 provideDelegate,可以在属性委托之前做一些额外的判断工作,例如可以根据委托属性的名字做不同的处理逻辑。

手写法

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class StringDelegate(private var str: String) : ReadWriteProperty<Owner, String> {
    override operator fun getValue(thisRef: Owner, property: KProperty<*>): String = "$str - fromDelegate"
    override operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
        println("setValue is called")
        str = "$value - setValue"
    }
}

class SmartDelegator {
    operator fun provideDelegate(thisRef: Owner, property: KProperty<*>): ReadWriteProperty<Owner, String> {
        println("provideDelegate is called")
        return if (property.name.contains("log")) StringDelegate("log-provideDelegate")
        else StringDelegate("normal-provideDelegate")
    }
}

class Owner {
    var normalText: String by SmartDelegator() // 将 normalText 委托给 SmartDelegator 的一个实例
    var logText: String by SmartDelegator()    // 将 logText 委托给 SmartDelegator 的一个实例
}

接口法

上面的 SmartDelegator 中的 provideDelegate 方法,实际上是 kotlin.properties.PropertyDelegateProvider 接口中声明的方法,我们也可以通过实现这个接口来实现这个方法:

import kotlin.properties.PropertyDelegateProvider

class SmartDelegator : PropertyDelegateProvider<Owner, ReadWriteProperty<Owner, String>> {
    override operator fun provideDelegate(thisRef: Owner, property: KProperty<*>): ReadWriteProperty<Owner, String> {...}
}

使用效果

fun main() {
    val owner = Owner()
    println("-----------------------")
    println(owner.normalText)
    println("-----------------------")
    owner.logText = "xxx"
    println(owner.logText)
}
provideDelegate is called
provideDelegate is called
-----------------------
normal-provideDelegate - fromDelegate
-----------------------
setValue is called
xxx - setValue - fromDelegate

可以看到,为了在委托属性的同时进行一些额外的逻辑判断,我们创建了一个 SmartDelegator,在其 provideDelegate 方法中,我们进行了一些逻辑判断,然后再把属性委托给 StringDelegate。

通过 provideDelegate 这样的方式,我们不仅可以嵌套 Delegator,还可以根据不同的逻辑派发不同的 Delegator。

委托实战案例

属性可见性封装

对于某个成员变量 data,如果我们希望类的外部可以访问它的值,但不允许修改它的值,可以这样写:

class Model {
    var data: String = ""
        private set // 将属性的 set 方法声明为 private,这样类的外部就只能访问、而不能修改
}

然而,将上面 data 的类型从 String 变成集合以后,问题就不一样了:

class Model {
    val data: MutableList<String> = mutableListOf() // 定义成 var + private set 也一样有下面的问题
}

Model().data.add("World") // 类的外部仍然可以修改 data

对于集合而言,即使我们将其定义为只读变量 val,仍然可以调用集合的 add() 方法修改它的值。可以利用两个属性之间的委托语法解决这个问题。

class Model {
    val data: List<String> by ::_data // 不可修改的集合,将其 getter 方法委托给私有的 _data
    private val _data: MutableList<String> = mutableListOf() // 可变集合,外部无法直接访问
    fun load() {
        _data.add("Hello") // 类的内部可以访问【可变集合】,所以类的内部可以修改数据
    }
}

fun main() {
    Model().data[0] // 类的外部只可以访问【不可修改的集合】,所以类的外部只可以访问数据
}
  • List 是 Kotlin 中不可修改的集合,它没有 add、remove 等方法
  • MutableList 是 Kotlin 中的可变集合,它有 add、remove 方法

通过上面这种方式,我们就成功地将修改权保留在了类的内部。

数据与 View 的绑定

DataBinding 可用于对 Android 中的数据View进行绑定,借助 Kotlin 的自定义委托属性,也可以实现类似的功能。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class TextView {
    var text: String? = ""
}

operator fun TextView.provideDelegate(value: Any?, property: KProperty<*>) = object : ReadWriteProperty<Any?, String?> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): String? = text // 内部类中可以访问外部类的成员
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
        text = value
    }
}
  • 首先为 TextView 定义了一个扩展函数 provideDelegate
  • 这个扩展函数的返回值类型是 ReadWriteProperty,泛型为 <Any?, String?>
    • 泛型 Any? 意味着,被委托属性所属类的可以是任意类,也就是说:可以委托任意类
    • 泛型 String? 意味着,被委托属性只能是 String? 类型(或者其父类型)
    • 类型 ReadWriteProperty 意味着,被委托属性可以是 var、也可以是 val
    • 总结来说就是:TextView 可以委托任意类的 String? 属性
val textView = TextView()
var message: String? by textView // 将 message 委托给 textView

textView.text = "Hello"
println(message) // Hello

message = "World"
println(textView.text) // World

ViewModel 委托

在 Android 当中,我们会经常用到 ViewModel 来存储界面数据。同时,我们不会直接创建 ViewModel 的实例,而对应的,我们会使用委托的方式来实现。

// MainActivity.kt
private val mainViewModel: MainViewModel by viewModels() // 将只读属性 mainViewModel 委托给了一个方法

viewModels() 的实现逻辑如下:

public inline fun <reified VM : ViewModel> ComponentActivity.viewModels( // 高阶函数
    noinline factoryProducer: (() -> Factory)? = null // 参数是一个默认值为 null 的函数
): Lazy<VM> { // 返回值类型是 Lazy
    val factoryPromise = factoryProducer ?: { defaultViewModelProviderFactory }  // 如果参数为空...
    return ViewModelLazy(VM::class, { viewModelStore }, factoryPromise) // 返回的是 Lazy 的一个实现类
}

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value // 扩展函数
  • 首先为 ComponentActivity 定义了一个扩展函数 viewModels(),所以才可以在 Activity 中直接调用这个方法
  • 扩展函数 viewModels() 是一个高阶函数,参数是一个默认值为 null 的函数
  • 扩展函数 viewModels() 的返回值类型是 Lazy,根据委托语法,被委托类 Lazy 必须要有 getValue() 方法
  • 由于在 Lazy 类的外部定义了一个扩展函数 getValue(),所以就满足委托的语法了

Android 官方这样的代码设计,就再一次体现了职责划分、关注点分离的原则。Lazy 类只包含核心的成员,其他附属功能,以扩展的形式在 Lazy 外部提供。

小结

  • 委托类,委托的是接口的方法,它在语法层面支持了委托模式
  • 委托属性,委托的是属性的 getter、setter,借助这个特性可以设计出非常复杂的代码
  • Kotlin 官方还提供了几种标准的属性委托
    • 两个属性之间的直接委托,在属性版本更新、可变性封装上,有着很大的用处
    • by lazy 懒加载委托,可以让我们灵活地使用懒加载
  • 自定义委托需要遵循 Kotlin 提供的一套语法规范
  • 自定义委托时可以使用 provideDelegate 动态调整委托逻辑

2016-12-12

原文地址:https://www.cnblogs.com/baiqiantao/p/6165143.html