zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Overloaded的方法是否可以改变返回值的类型
    parseXXX的用法
    java的类型转换问题。int a = 123456;short b = (short)a;System.out.println(b);为什么结果是-7616?
    UVA 10405 Longest Common Subsequence(简单DP)
    POJ 1001 Exponentiation(大数处理)
    POJ 2318 TOYS(计算几何)(二分)
    POJ 1265 Area (计算几何)(Pick定理)
    POJ 3371 Flesch Reading Ease (模拟题)
    POJ 3687 Labeling Balls(拓扑序列)
    POJ 1094 Sorting It All Out(拓扑序列)
  • 原文地址:https://www.cnblogs.com/baiqiantao/p/6165143.html
Copyright © 2011-2022 走看看