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

参考资料

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

相关推荐
聚美智数2 小时前
智能结构化识别-图片结构解析-图像结构化解析-照片结构化解析
android·java·数据库
mmsx7 小时前
Android 插件化架构:40 个 Gradle 模块的拆分、通信与共存
android·kotlin
mmsx7 小时前
Android 不改一行源码:用 AspectJ 做三类拦截
android·kotlin
TST-魚爷7 小时前
易视讯会议摄像头 + 全向麦克风实操讲解稿
android·电脑·钉钉·腾讯会议·zoom会议
嘟哩DuliDuli7 小时前
AI创作工作区的项目上下文与多模态任务边界
android·人工智能·安全·ai·软件工程
imDwAaY9 小时前
MySQL 存储引擎是什么?
android·数据库·mysql
熊猫钓鱼>_>10 小时前
开源鸿蒙平台 KMP 三方库 kotlin-csv 适配全流程:从编码假设到沙箱真机互通验证
开发语言·人工智能·华为·kotlin·harmonyos·鸿蒙
Meteors.10 小时前
Android Compose学习02:基础组件与 Modifier
android·学习
其实防守也摸鱼11 小时前
存储管理:页面置换算法学习任务(测验题要求给出置换页面顺序,缺页次数,缺页率)
android·前端·数据库·学习·oracle·自动化
mmsx12 小时前
Android 地图上绘制 KML:解析、样式与渲染
android·xml