理解并运行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) }

参考资料

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

相关推荐
独行soc14 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
王码码203514 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
2501_9151063214 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview
vistaup15 小时前
OKHTTP 默认构建包含 android 4.4 的TLS 1.2 以及设备时间不对兼容
android·okhttp
常利兵15 小时前
ButterKnife在Android 35 + Gradle 8.+环境下的适配困境与现代化迁移指南
android
撩得Android一次心动15 小时前
Android LiveData 全面解析:使用Java构建响应式UI【源码篇】
android·java·android jetpack·livedata
熊猫钓鱼>_>15 小时前
移动端开发技术选型报告:三足鼎立时代的开发者指南(2026年2月)
android·人工智能·ios·app·鸿蒙·cpu·移动端
Rainman博1 天前
WMS-窗口relayout&FinishDrawing
android
baidu_247438611 天前
Android ViewModel定时任务
android·开发语言·javascript
有位神秘人1 天前
Android中Notification的使用详解
android·java·javascript