理解并运用Kotlin的委托机制

公众号「稀有猿诉」        原文链接 Understanding Kotlin Delegation

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

Delegation Pattern

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

委托与代理的区别

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

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

实现委托

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

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就可以完成委托,编译器会自动生成受托类的实现,它的每个方法就直接调用委托对象的方法,可以理解 为上面的代码会编译生成这要的字节码:

class Derived extends Base {
    private Base impl;
    override void print() {
         impl.print();
    }
}

属性委托

class Example {
    var p: String by Delegate()
}

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

延时机制(lazy)

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

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

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

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

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来委托给一个函数。

var expand by remember { mutableStateOf(true) }

参考资料

欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!

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

  • 26
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值