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

参考资料

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

相关推荐
还鮟4 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡5 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi005 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil7 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你7 小时前
Android View的绘制原理详解
android
移动开发者1号10 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号10 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best15 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk15 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭20 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin