kotlin-委托

2021-10-29  本文已影响0人  Method

[toc]

委托是什么

委托又可以称为代理。为其他对象提供一种代理以控制对这个对象的访问,简单的说就是在访问和被访问对象中间加上的一个间接层,以隔离访问者和被访问者的实现细节。

类委托

类委托其实对应于Java中的代理模式

interface Base{
    fun text()
}

//被委托的类(真实的类)
class BaseImpl(val x :String ) : Base{
    override fun text() {
        println(x)
    }
}

//委托类(代理类)
class Devices (b :Base) :Base by b


fun main(){
    var b = BaseImpl("我是真实的类")
    Devices(b).text()
}

输出

我是真实的类

可以看到,委托类(代理类)持有真实类的对象,然后委托类(代理类)调用真实类的同名方法,最终真正实现的是方法的是真实类,这其实就是代理模式

kotlin中委托实现借助于by关键字,by关键字后面就是被委托类,可以是一个表达式

接口委托

如果接口有很多方法,子类就必须全部实现。

interface Api{
    fun a()
    fun b()
    fun c()
}

class ApiWrapper:Api{
    override fun a() {
    }

    override fun b() {
    }

    override fun c() {
    }
}

kotlin可以找个对象代替你实现它,我们只需要实现需要的方法即可。

class ApiWrapper(val api:Api):Api by api{
    override fun a() {
        println("判断是否登录")
        api.a()
    }
}

设计一个好玩的例子

class SupperArray<T>(val list: MutableList<T> = mutableListOf(), val map: MutableMap<String, T> = mutableMapOf()) :
    MutableList<T> by list, MutableMap<String, T> by map{

    override fun clear() {
        list.clear()
        map.clear()
    }

    override fun isEmpty(): Boolean {
        return list.isEmpty() && map.isEmpty()
    }

    override val size: Int
        get() = list.size+map.size

    override fun toString(): String {
        return "$list  $map"
    }
}

这是一个超级集合,包含一个list和一个map。这里用了代理,不需要实现全部的方法。只需要提供必要的方法。

fun main(args: Array<String>) {
    val supperArray = SupperArray<String>()
    supperArray["a"]= "hello"
    supperArray.add("world")
    println(supperArray.toString())
}

打印

[world]  {a=hello}

属性委托

属性委托和类委托一样,属性的委托其实是对属性的==set/get==方法的委托,把set/get方法委托给==setValue/getValue==方法,因此被委托类(真实类)需要提供==setValue/getValue==方法,val属性只需要提供==setValue==方法

属性委托语法:

val/var <属性名>: <类型> by <表达式>


class B {
    //委托属性
    var a : String by Text()
}

//被委托类(真实类)
class Text {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "属性拥有者 = $thisRef, 属性的名字 = '${property.name}' 属性的值 "
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("属性的值 = $value 属性的名字 =  '${property.name}' 属性拥有者 =  $thisRef")
    }
}

fun main(){
    var b = B()

    println(b.a)

    b.a = "ahaha"
    
}

输出

属性拥有者 = com.example.lib.weituo.B@27fa135a, 属性的名字 = 'a' 属性的值 
属性的值 = ahaha 属性的名字 =  'a' 属性拥有者 =  com.example.lib.weituo.B@27fa135a

上面的例子可以看到 ,属性a 委托给了Text,而且Text类中有setValue和getValue,所以当我们调用属性a的get/set方法时候,会委托到Text的setValue/getValue上,上面的例子可以看出来,里面有几个参数介绍一下:

thisRef:属性的拥有者
property:对属性的描述,是KProperty<*>类型或者父类
value:属性的值

更加简单的实现属性委托

每次实现委托都要写getValue/setValue方法,这就比较麻烦了,系统为我们提供了接口,方便我们重写这些方法,ReadOnlyProperty和ReadWriteProperty

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

Koltin 标准库中提供的几个委托

延迟属性(lazy properties):其值只在访问时初始化,再次调用直接返回结果
可观察属性(observable properties):监听器会收到此属性的变更通知
把多个属性映射到(Map)中,而不是存在单个字段

延迟属性lazy

lazy()接收一个lambda,返回Lazy实例,返回的实例可以作为实现延迟属性的委托,仅在第一次调用属性进行初始化

class Lazy {
    val name :String by lazy {
        println("第一次调用初始化")
        "aa" }
}

fun main(){
    var lazy =Lazy()
    println(lazy.name)
    println(lazy.name)
    println(lazy.name)
}

输出

第一次调用初始化
aa
aa
aa

lazy委托参数

SYNCHRONIZED:添加同步锁,使lazy延迟初始化线程安全
PUBLICATION:初始化的lambda表达式,可以在同一时间多次调用,但是只有第一次的返回值作为初始化值
NONE:没有同步锁,非线程安全
1. by lazy是通过属性代理来实现的懒加载,只在第一次使用的时候才会去执行表达式,并且只会执行一次。
2. by lazy默认是线程安全的,内部通过双重判断锁来保证只执行一次代码块赋值
3. 当能够确定不会发生在多线程中的时候,可通过lazy(LazyThreadSafetyMode.NONE) { ... }来避免加锁。

lazy 原理

通过前面对属性委托的简单介绍,我们也明白了属性委托的机制。by后面跟着的肯定是一个对象,也就是委托对象,该对象负责属性的get/set,所以lazy这个方法最终肯定会返回一个对象。

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

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)
    }
    
public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)

lazy方法一共有三个,我们最常用的是第一个。从代码中可以看出,lazy方法最终是创建的Lazy<T>的实例,这个实例也就是属性委托的对象。

public interface Lazy<out T> {
    public val value: T
    public fun isInitialized(): Boolean
}

Lazy一共有三个子类,其中我们使用的第一个方法返回的是SynchronizedLazyImpl,这是Lazy的其中一个实现。

internal object UNINITIALIZED_VALUE

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    
    // 委托的属性的值由_value记录,初始值是单例对象UNINITIALIZED_VALUE
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            // 已初始化则直接返回对应值
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            
            // 未初始化的,执行传递进来的lambda参数进行赋值
            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
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

SynchronizedLazyImpl是Lazy的一个子类,可以看到在value属性的get方法中,会去判断是否已经初始化过,若是没有初始化,则会调用initializer去进行初始化,赋值给_value。若是已经初始化过,则会直接返回 _value的值。因此by lazy就能通过这种方式去实现懒加载,并且只加载一次。

从代码中可以看出,SynchronizedLazyImpl是保证了线程安全的。是通过DCL方式来保证的安全,能够确保在多线程下也只会执行一次代码块。

但是我们说过,要实现可读属性委托,必须实现getValue方法。而在SynchronizedLazyImpl中,并没有实现getValue方法,而是只有value属性的get方法。这里猜测编译器应该是对Lazy有特殊的处理,而通过实验,实现了Lazy接口的对象确实可以作为只读属性的委托对象。而其他接口即使与Lazy一模一样,实现它的对象也不能作为属性委托对象。

可观察属性Observable委托

可以观察一个属性的变化过程

class Lazy {
   
    var a : String by Delegates.observable("默认值"){
            property, oldValue, newValue ->

        println( "$oldValue -> $newValue ")
    }
}

fun main(){
    var lazy =Lazy()


    lazy.a = "第一次修改"

    lazy.a = "第二次修改"
}

输出

默认值 -> 第一次修改 
第一次修改 -> 第二次修改

vetoable委托

vetoable和Observable一样,可以观察属性的变化,不同的是vetoable可以决定是否使用新值

class C {
    var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
        println("oldValue = $oldValue -> oldValue = $newValue" )
        newValue > oldValue


    }
}

fun main() {
    var c = C()

    c.age = 5
    println(c.age)

    c.age = 10
    println(c.age)

    c.age = 8
    println(c.age)

    c.age = 20
    println(c.age)
}

输出

oldValue = 0 -> oldValue = 5
5
oldValue = 5 -> oldValue = 10
10
oldValue = 10 -> oldValue = 8
10
oldValue = 10 -> oldValue = 20
20

当新值小于旧值,那么就不生效,可以看到第三次设的值是8,小于10就没有生效

属性储存在Map中

class D(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

fun main(){
    var d = D(mapOf(
        "name" to "小明",
        "age" to 12
    ))

    println("name = ${d.name},age = ${d.age}")
}

输出

name = 小明,age = 12

上一篇下一篇

猜你喜欢

热点阅读