Kotlin的委托机制

Kotlin 的委托机制在语言层面自动实现了 Java 的组合代理。Kotlin 的委托包括委托类、委托属性,使用 by 关键字表示委托。

委托类

假设有一个接口类 Db,用来保存数据。

interface Db {
    fun save()
}

Db 有两个具体的实现类 SqlDb 和 GreenDaoDb

class SqlDb : Db {
    override fun save() {
        println("save sql db")
    }
}

class GreenDaoDb : Db {
    override fun save() {
        println("save green dao db")
    }
}

如果使用 Java 风格的委托方式,会这么写:

class MyDb(private val db: Db) : Db {
    override fun save() {
        db.save()
    }
}

这种方式有很多样板代码,比如重写 save(),在接口方法调用属性 db 的 save()。

Kotlin 的 委托机制使用 by 关键字,自动帮我们省去了样板代码。

只需要使用 by 关键字表明委托给了谁。

class UniversalDb(db: Db) : Db by db

Db 接口的所有方法都交给 by 关键字后面的 db 实现。

它等价于:

class UniversalDb(private val db: Db) : Db {
    override fun save() {
        db.save()
    }
}

委托类的使用如下:

fun main() {
    // delegate class
    println("\ndelegate class")
    UniversalDb(SqlDb()).save()
    UniversalDb(GreenDaoDb()).save()
}

将任意一种 Db 接口的实现类作为委托类 UniversalDb 的入参,UniversalDb 的 save 方法实际是由入参实现的。

总结:委托类帮我们自动实现了接口的方法。

委托属性

如果说委托类实现了接口方法,那么委托属性实现了属性的 getter/setter 方法。

Item 类将它的 total 属性委托给了 count 属性。

/**
 * 委托属性
 * 两个属性之间的直接委托。Kotlin 1.4新特性
 */
class Item {
    var count: Int = 0
    var total: Int by ::count
}

注意委托属性的 by 关键字后面是一个双冒号 :: ,表示属性引用操作符。

假如不写 by ::count,而是写 by count,编译器会报错提示:

class Item {
    var count: Int = 0
    var total: Int by count // 报错
}
Type 'Int' has no method 'getValue(Item, KProperty<*>)' and thus it cannot serve as a delegate
Type 'Int' has no method 'setValue(Item, KProperty<*>, Int)' and thus it cannot serve as a delegate for var (read-write property) 

编译器提示 Int 类型没有实现 getValue 和 setValue 方法,不能作为委托属性。

而 ::count 属性引用是 KMutableProperty0 类,它帮我们实现了 get 和 set。

item 的 count 值改变时,委托属性 total 也跟着改变。

fun main() {
    // delegate property
    println("\ndelegate property")
    val item = Item()
    item.count = 2
    println("total:${item.total}")

}

懒加载委托

kotlin 提供了 lazy 方法实现懒加载委托,也就是只在 data 第一次被使用的时候才开始加载。

/**
 * 懒加载委托
 */
val data: String by lazy {
    request()
}

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

第一次 println(data) 会打印 “执行网络请求”,而第二次不会。因为使用 lazy 方法将 request 的结果保存,第二次打印 data 时,直接返回 reqeust 结果,而不是再次执行 request。

fun main() {
    // lazy delegate
    println("\nlazy delegate")
    println("开始")
    println(data)
    println(data)
}
lazy delegate
开始
执行网络请求
网络数据
网络数据

kotlin 的 lazy 方法定义在 LazyJVM.kt,返回一个 SynchronizedLazyImpl 类的示例。

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

SynchronizedLazyImpl 实现了一个单例模式,如果 _value 初始化过,直接返回 _value 的值,否则使用 lazy 的入参函数 initializer 初始化,返回 T 类型的值,并赋值给 _value。

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
   ...

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                ...
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }
}

自定义委托

除了标准的委托属性,kotlin 还提供了自定义委托的方式实现委托属性。

以 Owner 为例,它的 text 属性委托给了 StringDelegate,textReadWrite 属性委托给了 StringReadWritePropertyDelegate。这两个都是自定义委托。

需要注意的是两者都使用了括号 () 操作符,因为委托需要指定一个对象示例。

class Owner {
    var text: String by StringDelegate()
    var textReadWrite: String by StringReadWritePropertyDelegate()
}

StringDelegate 实现了 getValue 和 setValue 方法,用来实现被委托属性的 getter 和 setter。 getValue 和 setValue 方法的入参、返回值等签名信息必须严格按照格式来写,否则编译不过。

thisRef 表示属性所在的类,property 表示被委托的属性,value 表示 setter 需要设置给属性的值。

/**
 * 自定义委托
 */
class StringDelegate(private var s: String = "Hello") {

    /**
     * getValue 操作符
     * thisRef 属性所在的类的类型
     * KProperty 表示一个属性,例如命名的 val 或者 var 声明。此类的实例可由 :: 运算符获取
     * 返回值 String
     */
    operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
        return s
    }

    /**
     * setValue 操作符
     * thisRef 属性所在的类的类型
     * KProperty 表示一个属性,例如命名的 val 或者 var 声明。此类的实例可由 :: 运算符获取
     * value 属性的值,类型为 String
     */
    operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
        s = value
    }
}

为了简化自定义委托的实现,kotlin 提供了一些标准属性接口。

ReadWriteProperty

ReadWriteProperty 可读写属性接口,它的签名和上面自己写的 StringDelegate 是一样的。

/**
 * 使用 ReadWriteProperty接口实现委托属性,使用了泛型
 */
class StringReadWritePropertyDelegate(private var s: String = "Hello custom") :
    ReadWriteProperty<Owner, String> {
    override fun getValue(thisRef: Owner, property: KProperty<*>): String = s

    override fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
        s = value
    }
}

ReadOnlyProperty

如果是只读属性,可以实现 ReadOnlyProperty。

public fun interface ReadOnlyProperty<in T, out V> {
    /**
     * Returns the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @return the property value.
     */
    public operator fun getValue(thisRef: T, property: KProperty<*>): V
}

使用自定义委托:

    // custom delegate property
    println("\ncustom property delegate")
    val owner = Owner()
    println(owner.text)
    println(owner.textReadWrite)

输出如下:

custom property delegate
Hello
Hello custom

PropertyDelegateProvider

除了上述可读写、只读的委托属性,Kotlin 还提供了委托属性提供者 PropertyDelegateProvider,用来提供委托属性。

SmartDelegator 实现了 PropertyDelegateProvider 的 provideDelegate,根据属性名称动态返回委托属性。

class SmartDelegator : PropertyDelegateProvider<Owner, StringDelegate> {
    override fun provideDelegate(thisRef: Owner, property: KProperty<*>): StringDelegate {
        return if (property.name.contains("log")) {
            StringDelegate("log")
        } else {
            StringDelegate("cat")
        }
    }
}

使用方法和委托属性一样:

class Owner {
    var log by SmartDelegator()
    var cat by SmartDelegator()
    var text: String by StringDelegate()
    var textReadWrite: String by StringReadWritePropertyDelegate()
}

fun main() {
    // provide delegate
    println(owner.log)
    println(owner.cat)
}

根据属性名称输出:

log
cat

总结

kotlin 使用 by 关键字实现了委托模式。kotlin 的委托有委托类、委托属性、懒加载委托、自定义委托。

自定义委托有 ReadOnlyProperty、ReadWriteProperty、PropertyDelegateProvider 三个接口,方便快速实现委托属性。

使用委托可以简化样板代码,写起来更加简洁。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值