Kotlin - 委托(by、Delegate)、懒加载 by lazy()

一、概念

委托模式是一个已经存在的设计模式,装饰者模式和代理模式都通过把类的一部分功能委托给一个对象复用了行为,Kotlin 在语言层面支持了委托。设置点击监听就是委托给一个匿名内部类对象。

装饰者模式ContextWrapper 继承了 Context 把需要实现的方法都委托给了内部的一个 Context 类型的成员变量。 
代理模式ArrayList 和 LinkedList 对 List 接口做出不同的内部实现以应对不同的性能需求,这种对于接口的核心功能实现是不需要委托模式的,当需要对 List 接口定制额外功能如元素类型为 User 有各种与用户相关的功能时,不需要分别继承 ArrayList 和 LinkedList 提供两套方案,而是直接实现 List 接口并在内部设置一个 List 类型的成员变量(具体用 ArrayList 还是 LinkedList 都无所谓,通过构造从外部传入赋值像插件一样)让它作为核心功能的代理,然后就可以自行随意添加功能了。

二、接口委托

把类实现接口需要重写的方法,部分或完全委托给一个成员对象来实现,可以让该实现类不用关心接口核心功能的具体实现而是额外的功能定制和扩展。JAVA 需要在大量需要重写的方法中调用委托对象的功能,而 Kotlin 不用写这些模板代码。

//定义接口
interface Function {
    fun show()
}
//定义实现类
class FunctionImpl : Function {
    override fun show() {...}
}
//将要实现的接口委托给实现类
class MyActivity : ComponentActivity(), Function by FunctionImpl() {
    onCreate() {
        show()    //调用方法
    }
}
interface Function {
    fun run(): String
    fun jump(): String
}

class Model1 : Function {
    override fun run(): String = "run"
    override fun jump(): String = "jump"
}
//装饰者模式/代理模式
//重写时调用持有的实例去做
class Model2(val function: Function) : Function {    //持有一个实例
    override fun run() = function.run() + " fast"    //对实例加强
    override fun jump() = function.jump()            //委托实例做
}
//Kotlin委托
//实现Function接口需要的重写,委托给持有的实例去做
class Model3(val function: Function) : Function by function{
    //加强的功能还是需要自己重写
    //手动重写需要实现的方法就不会交给委托对象了
    //完全委托的功能可以不写,减少了模版代码
}

val model3 = Model3(Model1())
model3.run()

三、属性委托

将属性的访问器委托给另一个对象,以减少模板代码并隐藏访问细节。

重载运算符重写getValue()、setValue()。
实现接口使用系统提供的Delegates、或者自己实现接口。
扩展方法像lazy那样通过扩展函数重载运算符。
Map委托直接委托给一个Map实例。

3.1 重载运算符

by约定:将属性的 getter/setter 交给别的对象重载运算符 getValue()/setValue() 去实现。也就是被 var 属性委托的对象的类要重写 getValue()、setValue()方法,被 val 属性委托的对象的类要重写 getValue() 方法。

//thisRef:属性所在类的类型
//property:属性
//value:属性的类型
operator fun getValue(thisRef: Any?, property: KProperty<*>)
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String)
class Phone {
    //一般是先委托再去被委托的类中重写,这样IDE能自动补全代码,手写也好去写类型
    val screen: String by ScreenFactory()    //val属性委托给屏幕对象
    var battery: String by BatteryFactory()  //var属性委托给电池对象
}

//被委托的屏幕类
class ScreenFactory {
    private val str: String = "屏幕"
    operator fun getValue(phone: Phone, property: KProperty<*>): String = str
}

//被委托的电池类
class BatteryFactory {
    private var str = "电池"
    operator fun getValue(phone: Phone, property: KProperty<*>): String = str
    operator fun setValue(phone: Phone, property: KProperty<*>, s: String) { str = s }
}

val phone = Phone()
println(phone.battery)    //打印:电池
phone.battery = "坏电池"
println(phone.battery)    //打印:坏电池

3.2 实现接口 Delegates

结构说明重点关注的地方
ReadOnlyPropertyval 属性代理对象的通用接口。

getValue( ) 用来代理属性的getter

ReadWritePropertyvar 属性代理对象的通用接口。

setValue( ) 增加用来代理属性的setter

NotNullVar

notNull( ) 返回的代理对象的类。

空判断
ObserableProperty

observable( ) 返回的代理对象的类。

afterChange( ) 赋值前判断是否允许

vetoable( ) 返回的代理对象的类。

beforeChange( ) 赋值后提供数据反馈

Degelates

是一个单例对象,通过不同方法返回不同的代理对象。

notNull( ) 返回一个NotNullVar对象

observable( ) 返回一个ObserableProperty对象

vetoable( ) 返回一个ObserableProperty对象

3.2.1 Delegates.notNull( )

可以不在属性声明的时候初始化,而是延迟到类中其他地方再初始化。属性委托后,编译器不会再做非空检查,不初始化也不会报错,所以需要做到自己可控(无论在类中还是类外要对属性赋值),否则在使用之前未初始化,调用会报错 IllegalStateException。

Delegates.notNull()

适用于:基本类型、引用类型。

不支持外部注入工具将它直接注入到Java字段中。

lateinit适用于:引用类型。

3.2.2 Delegates.observable( )

类似于观察者用于监听属性值发生变更,当属性值被更改后会往外抛出一个回调。

3.2.3 Delegates.vetoable( )

同上,不同的是回调会返回一个 Boolean 值,来决定此次修改是否通过。

class Demo {
    var num1: Int by Delegates.notNull()
    var num2: Int by Delegates.observable(1) { property, oldValue, newValue -> println("发生了变化:${property.name},$oldValue,$newValue") }
    //新值是负数才能修改成功
    var num3: Int by Delegates.vetoable(1) { property, oldValue, newValue -> newValue < 0 }
}

val demo = Demo()
demo.num1 = 3
println(demo.num1)    //打印:3
demo.num2 = 3
println(demo.num3)  //打印:发生了变化:num2,1,3  1
demo.num3 = 3
println(demo.num3)  //打印:1
demo.num3 = -3
println(demo.num3)   //打印:-3

2.3 by lazy( )

by 是关键字,lazy() 是高阶函数,形参 Lambda 用来创建目标对象。当我们调用属性的时候,就是在调用 Lazy.getValue()。

  • 延迟对象的初始化,直到第一次访问(使用)它,避免类加载的时候过多消耗资源。
  • 只能对 val 的属性使用。第一次调用会执行传入的 Lambda 并记录和返回结果,再次调用只返回之前记录的结果。
  • 线程安全。
Lazy.ktLazy 接口public interface Lazy<out T> {
    public val value: T        //懒加载的值,一旦被赋值件将不会被改变
    public fun isInitialized(): Boolean        //检查是否被初始化
}
by 关键字

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

by的作用就是把属性的getter/setter和被委托对象的类中重写的getValue()/setValue()配对,这里是Lazy的扩展函数形式。
LazyJVM.ktlazy() 函数

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)
    }

形参 initializer 接收一个 Lambda 返回一个 Lazy 的实现类实例,lazy() 有很多重载一般使用第一个,是同步的,原理双重校验锁。对于 Android 开发来说,大多数情况下都是在主线程调用 lazy() 的属性,使用同步的就会带来不必要的性能开销,可以考虑替换为 NONE 模式。
//这样写在实例初始化的时候就会初始化 gender 这个属性。
class Staff(val name: String, var position: String) {
    val gender: String = if (position == "Counter") "female" else "male"
}
//修改之后没有了= 赋值,只有在初次访问sex这个属性的时候,才会进行初始化。
class Staff(val name: String, var position: String) {
    val gender: String by lazy {
        if (position == "Counter") "female" else "male"
    }
}

2.4 Map委托 

更方便获取 map 值,Kotlin 标准库已经为 Map 定义了getValue()setValue()扩展函数。属性名将自动作用于 map 的键。

  • 属性名要和 Map 的 key 一致。
  • val可以委托给 Map 或 MutableMap,var只能委托给MutableMap。
class Person(mutableMap: MutableMap<String, Any?>) {
    val id: Int by mutableMap
    var name: String by mutableMap
}

fun main() {
    val mutableMap = mutableMapOf<String,Any?>("id" to 123456,"name" to "ZhangSan")
    val person = Person(mutableMap)
    println(person.id)  //打印:123456
    println(person.name)    //打印:ZhangSan
//    person.id = 654231  //对val属性再次赋值会报错
    person.name = "LiSi"
    println(person.name)    //打印:LiSi
}
class Demo {
    val attrs = mapOf<String, Int>()
    val name: String by attrs
}

三、使用场景/举例说明

3.1 创建ViewModel

viewModels() 是 ComponentActivity 的扩展函数,返回一个 Lazy 的实现类 ViewModelLazy。

val viewModel by viewModels<MainViewModel>()
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
    ...
): Lazy<VM> {
    ...
    return ViewModelLazy(...)    //返回一个Lazy的实现类
}

public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
...
) : Lazy<VM> {
    ...
    //重写
    override val value: VM
        get() {...}
    override fun isInitialized(): Boolean = cached != null
}

3.2 findViewById()

委托可以隐藏逻辑细节,特别是一些模板代码的时候.。利用扩展函数间接调用属性委托,可以增加一些逻辑操作、封装委托细节。

//被委托的类
class Find<T>(val id: Int) {
    operator fun getValue(activity: Activity, kProperty: KProperty<*>): T = activity.findViewById(id)
}

//也可以给Activity扩展一下
fun <T : View> Activity.find(id: Int) = Find<T>(id)

//使用
Activity {
    private val textView by find<TextView>(R.id.textView)    //通过扩展函数间接推脱
    private val button by Find<Button>(R.id.button)    //直接推脱
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值