委托模式

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
Kotlin直接支持委托模式,更加优雅,简洁。Kotlin通过关键字by实现委托。

属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。

属性委托语法格式:

val/var <属性名>: <类型> by <委托代理类>
属性委托要求
  • 对于只读属性(也就是说val属性), 它的委托必须提供一个名为getValue()的函数。该函数接受以下参数:

    • thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
    • property —— 必须是类型 KProperty<*> 或其超类型这个函数必须返回与属性相同的类型(或其子类型)。
  • 对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须 另外再提供一个名为setValue()的函数, 这个函数接受以下参数:

    • property —— 必须是类型 KProperty<*> 或其超类型
    • value —— 必须和属性同类型或者是它的超类型。

所以一个最基本的委托类代码如下:

class Delegate {
    private var str: String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("${property.name}属值为:$str")
        return str ?: "尚未赋值"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("${property.name}属性赋值为:$value")
        str = value
    }
}

fun main() {
    var p: String by Delegate()
    println(p)     // 访问该属性,调用 getValue() 函数
    p = "Runoob"   // 调用 setValue() 函数
    println(p)
}

输出:

p属值为:null
尚未赋值
p属性赋值为:Runoob
p属值为:Runoob
委托接口

Kotlin基本函数库里专门提供了两个委托接口,方便我们使用:

1. ReadOnlyProperty  //只读属性,针对val
2. ReadWriteProperty  //读写属性,针对var
标准委托
notNull

notNull适用于那些无法在初始化阶段就确定属性值的场合。

看到这句话我们基本都会想到关键字lateinit,但是我们知道lateinit不可用于基础数据类型。
而notNull可用基础数据类型,基础数据类型一般情况下为空是可直接取默认值,所以我们日常使用lateinit即可代替notNull。

    var notNullBar: Int by Delegates.notNull()
     notNullBar = 123
     println(notNullBar)
延迟属性Lazy

lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:
第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果,
后续调用 get() 只是返回记录的结果。

需要注意的是lazy只适用于val类型。

val lazyValue: String by lazy {
        println("computed!")     // 第一次调用输出,第二次调用不执行
        "Hello"
    }

    println(lazyValue)   // 第一次执行,执行两次输出表达式
    println(lazyValue)   // 第二次执行,只输出返回值

输出:

computed!
Hello
Hello

你可以将局部变量声明为委托属性。如下:
memoizedFoo 变量只会在第一次访问时计算。 如果someCondition失败,那么该变量根本不会计算。

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}
可观察属性值变化Observable

Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。
handler有三个参数:被赋值的属性、旧值和新值。

 var name: String by Delegates.observable("初始值") {
        prop, old, new ->
        println("旧值:$old -> 新值:$new")
    }
    name = "第一次赋值"
    name = "第二次赋值"

输出:

旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值
映射属性的值

经常出现在像解析 JSON 或者做其他"动态"事情的应用中。
在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

class Site(map: MutableMap<String, Any?>) {
    val name: String by map
    val url: String by map
}

fun main() {
    val site = Site(map)

    println(site.name)
    println(site.url)

    println("--------------")
    map.put("name", "Google")
    map.put("url", "www.google.com")

    println(site.name)
    println(site.url)
}

输出:

菜鸟教程
www.runoob.com
--------------
Google
www.google.com

Kotlin委托简化获取Intent参数的方式

前面简单介绍了委托及使用。接下来我们动手实战,使用委托来简化获取Intent参数的方式。

一般我们获取Intent参数,如果要全局使用时,需要先声明一个var变量,然后再onCreate中通过getXXXExtra()方法获取。大致代如下:

 private var test: String? = ""

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        test = intent.getStringExtra("test")
        
    }

如果我们直接这么写会,运行时会报错val test: String = intent.getStringExtra("test"),因为属性初始化的时候getIntent返回的是空,只有在onCreate之后调用才能保证不为空。

那我们换个写法:

private val test by lazy {
   intent.getStringExtra("test")
}

嗯,这样写就省事很多了,我们使用lazy延时加载的特性,避免了intent为空的情况。但是前面我们说过lazy只适用于val类型声明,属性不能修改,显然不是很符合我们日常开发的需求。

我们知道ReadWriteProperty接口适用于读写var类型的声明,因此我们可以实现ReadWriteProperty接口,来完成我们的需求。

class ActivityExtras<T>(private val extraName: String, private val defaultValue: T) : ReadWriteProperty<Activity, T> {
    /**
     * getExtras字段对应的值
     */
    private var extra: T? = null
    override fun getValue(thisRef: Activity, property: KProperty<*>): T {
        // 如果extra不为空则返回extra
        // 如果extra是空的,则判断intent的参数的值,如果值不为空,则将值赋予extra,并且返回
        // 如果intent参数的值也为空,则返回defaultValue,并且将值赋予extra
        return extra ?: thisRef.intent?.get<T>(extraName)?.also { extra = it }
        ?: defaultValue.also { extra = it }
    }

    override fun setValue(thisRef: Activity, property: KProperty<*>, value: T) {
        extra = value
    }
}

thisRef.intent?.get<T>(extraName)这段代码的get扩展函数见https://blog.csdn.net/u011387817/article/details/99844066,该文使用反射简化了获取intent参数是的类型,非常方便。
也可以访问源码参考。
当然你也可以根据泛型T的类型,使用is判断它的类型,然后自己根据种类逐个区分开来。这里不是重点,就不赘述了。

有了上面的实现,我们进一步使用扩展函数简化默认值:

fun <T> extraAct(extraName: String): ActivityExtras<T?> = ActivityExtras(extraName, null)

fun <T> extraAct(extraName: String, defaultValue: T): ActivityExtras<T> = ActivityExtras(extraName, defaultValue)

接下来在Activity中使用:

 var list: ArrayList<String> by extraAct("params", arrayListOf())

是不是非常的清爽? Fragment中实现也基本一致:

/**
 * 获取Intent参数,Fragment
 * 示例同[ActivityExtras]
 */
class FragmentExtras<T>(private val extraName: String, private val defaultValue: T) : ReadWriteProperty<Fragment, T> {

    /**
     * getExtras字段对应的值
     */
    private var extra: T? = null

    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
        // 如果extra不为空则返回extra
        // 如果extra是空的,则判断intent的参数的值,如果值不为空,则将值赋予extra,并且返回
        // 如果intent参数的值也为空,则返回defaultValue,并且将值赋予extra
        return extra ?: thisRef.arguments?.get<T>(extraName)?.also { extra = it }
        ?: defaultValue.also { extra = it }
    }

    override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
        extra = value
    }
}

fun <T> extraFrag(extraName: String): FragmentExtras<T?> = FragmentExtras(extraName, null)

fun <T> extraFrag(extraName: String, defaultValue: T): FragmentExtras<T> = FragmentExtras(extraName, defaultValue)

以上源码请见:Github源码

参考

https://www.runoob.com/kotlin/kotlin-delegated.html
https://blog.csdn.net/u011387817/article/details/99844066
https://www.jowanxu.top/2019/08/27/Kotlin_intent_get_extra/

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐