理解并运行Kotlin的委托机制

原文链接 Understanding Kotlin Delegation

委托或者说委派,是一种设计机制,实现者并不真正的实现某些方法(行为),而是让另外一个对象来当真正的实现者。委拖与依赖注入和延时加载技术结合在一起会产生非常巨大的威力,让代码不但灵活方便扩展,也非常的优雅,但确实会较难以理解。委托机制(Delegation)在Kotlin中的支持是很友好的,并且非常完善,用关键字by和lazy一起就可以写出非常强大的委拖机制代码。

Delegation Pattern

要想理解Kotlin中的委托,要先理解一下设计模式中的委托模式。严格来说委拖并不是一种设计模式,因为它并没有固定的范式,在GoF以及很多关于设计模式的书中并没有这一模式,准确的来说它是一种行为的实现方式,并不自己直接实现,而是委派给另外一个对象的方法。委托是一种行为模式,它只注重于行为,一般情况下都是对对象的方法进行委托,或者行为产生的结果也就是一个变量或者对象的域也可以委托,但只能委托给一个函数,这个函数会产生结果,以得到域的值。

委托与代理的区别

代理是一种正式的设计模式,它强调的是权限和隔离,client只能访问到proxy,而并不知道realObject。而委托是一种实现机制,不自己实现,委派给其他对象去实现,它更强调的是行为和结果。代理是一种委托机制,但委托并不是代理。

在理解了委托的概念后,就可以进一步的来看一下Kotlin中的委托了。

实现委托

就是某一个类的实现,完全委托给另外一个对象,为了保持行为的一致,它们都实现了某一个接口。用关键字by来实现这一委托机制:

kotlin 复制代码
interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}

类Drived也实现了接口Base,但它并没有自己去实现方法,而是委派给了它的构造参数b,b也是一个实现了接口的对象。这样Drived的行为就都委托给了对象b。而b则可以是任何一个实现了Base接口的对象,并且是在创建Drived时才指定的。更进一步的,这里可以用工厂方法,因为只要能生成一个实现了Base接口的对象即可,甚至可以用依赖注入来动态生成对委托对象。

这里需要明确一下术语,委托给别人的对象称为受托对象或者受托类,真正的做事情的人称之为委托对象。

Kotlin仅用一个关键字by就可以完成委托,编译器会自动生成受托类的实现,它的每个方法就直接调用委托对象的方法,可以理解 为上面的代码会编译生成这要的字节码:

java 复制代码
class Derived extends Base {
    private Base impl;
    override void print() {
         impl.print();
    }
}

属性委托

kotlin 复制代码
class Example {
    var p: String by Delegate()
}

属性委托是把属性的getter/setter委托给某一个函数,或者某一个对象(这个对象要有setValue/getValue方法,本质上仍是委托给一个函数)。

延时机制(lazy)

延时机制的委托才能产生最大的威力,而在Kotlin中,借助by和lazy就能对对象的属性实现延时委托机制,让只有在必要的时候(即第一次访问这个属性的时候)才生产出属性的真实值。

kotlin 复制代码
val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}

其实,by lazy可以应用在任何地方,不光是属性,常规的变量也是可以的。另外,需要注意lazy不但是第一次用到此变量时才会此具体计算,而且也只计算一次,后续再访问时,会从cache中读取首次计算后的值:

kotlin 复制代码
var foo by lazy {
    if (someCondition) {
         generate()
    } else {
         defaultValue
    }
}

这个例子,假如第一次访问变量foo时,执行lazy后时someCondition是false就会返回defaultValue,而且后续再访问foo也不会再执行这个尾部lambda了,会直接返回defaultValue,因为它被cache住了。所以一般用lazy都是要针对 只读变量,也即val foo by lazy { ... },这才是最正统的用法。

需要注意,lazy并不是一个关键字,它是一个函数,它只有一个参数就是一个lambda,所以可以写成尾部lambda的方式。

常规委托

使用by关键字就可以实现委托,这除了可以用于类的实现,属性实现以外,其实任何一个变量也可以用by来委托给一个函数。

kotlin 复制代码
var expand by remember { mutableStateOf(true) }

参考资料

原创不易,打赏点赞在看收藏分享 总要有一个吧

相关推荐
带电的小王1 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡1 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道2 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库2 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道3 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe3 小时前
Android Hook - 动态加载so库
android
居居飒4 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He7 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗7 小时前
Android笔试面试题AI答之Android基础(1)
android
qq_397562318 小时前
android studio更改应用图片,和应用名字。
android·ide·android studio