委托
介绍
类委托允许一个类将自身的部分实现委托给另一个类完成。
委托模式提高了代码复用性,通过委托可将通用逻辑提取到其他类中, keeping 使原类职责更加单一清晰。并且可以非侵入式地扩展类的功能。
优点
- 可以消除冗余代码: 委托模式让两个类建立关联,可以提取公共代码到委托类中,消除冗余。
- 提高代码复用: 委托类封装通用功能和行为,可以被多个其他类重复使用。
- 增强扩展性: 可以通过委托类继承和组合来扩展功能,而无需修改原有类。
- 实现声明式设计: 委托后的类接口更加简洁,只关注自身逻辑。
- 符合单一职责原则: 委托类只负责被抽象的共性功能,原类保留业务聚焦。
- 统一维护: 可以在委托类中集中维护公共代码,一处修改多处生效。
类委托
在买火车票的时候,经常会通过第三方平台进行购买,我们提供钱,他们帮我们购票抢票,人物本身不需要知道里面的具体逻辑
interface TicketPlatform {
fun printTicket(money: Int) //出票
}
open class XcTicket : TicketPlatform {
override fun printTicket(money: Int) {
println("XcTicket bought you a ticket for $money yuan ~")
}
}
class User(ticketPlatform: TicketPlatform) : TicketPlatform by ticketPlatform {
}
//测试
val xcTicket = XcTicket()
//将User的购票逻辑 ,委托给协程购票,本身不关注该逻辑业务,保证User类职责更加清晰
User(xcTicket).printTicket(10) //XcTicket bought you a ticket for 10 yuan ~
总结: 上面例子基本满足了委托的所有优点,User无需通过手动TicketPlatform的实例,也可以直接调用TicketPlatform的方法(类似于继承),众所周知kotlin 和 java 一样,都只能单继承,但是通过委托,User 可以实现多委托效果,从而达到多继承效果
属性委托
by 不仅可以让类委托,也可以让属性进行委托,属性委托更常见,它可以将属性的访问器逻辑委托给其他类处理,语法格式
val/var <属性名>: <类型> by <表达式>
val/var name: String by Delegate()
官方常见三种属性委托:
- 延迟属性(lazy properties): 其值只在首次访问时计算。
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知。
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
属性委托的原理
在 by 后面的表达式是该 委托的实现, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现接口,但是需要提供一个 getValue() 函数(对于 var 属性还有 setValue())
// 委托的属性
var workName :String by WorkNameDelegate()
// 被委托属性的Set/Get实现
class WorkNameDelegate {
private var value : String = "Worker"
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef ,他的 默认 ${property.name} 是 $value !"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
this.value = value
println("$thisRef ,他的 ${property.name} 被设置为 $value !")
}
}
//测试
fun main() {
val worker = Worker()
println( worker.workName)
worker.workName = "Android Developer Manager"
println( worker.workName)
}
//输出
com.gx.kt.study.by.cls.Worker@50f8360d ,他的 默认 workName 是 Worker !
com.gx.kt.study.by.cls.Worker@50f8360d ,他的 workName 被设置为 Android Developer Manager !
com.gx.kt.study.by.cls.Worker@50f8360d ,他的 默认 workName 是 Android Developer Manager !
其中的参数解释如下:
- thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;
- property —— 必须是类型 KProperty<*>或其超类型。(类似于java 中的Field)
- value —— 必须与属性同类型或者是它的子类型。
另一种自定义属性委托的方式
要实现属性委托,就必须要提供getValue/setValue方法,对于比较懒的同学可能就要说了,这么复杂的参数,还要每次都要手写,真是麻烦,一不小心就写错了。确实是这样,为了解决这个问题, Kotlin 标准库中声明了2个含所需 operator方法的 ReadOnlyProperty / ReadWriteProperty 接口。
interface ReadOnlyProperty<in R, out T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
被委托类 实现这两个接口其中之一就可以了,val 属性实现ReadOnlyProperty,var属性实现ReadOnlyProperty。
Kotlin 标准库中提供几个委托
Kotlin 标准库中提供了几种委托,例如:
- 延迟属性(lazy properties): 其值只在首次访问时计算;
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
延迟属性 by lazy
lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
val age : Int by lazy {
var realAge :Int = 10
realAge = 10 + 1
println("计算年龄 $realAge ") // 只有第一次调用会计算,后面会缓存在age里
realAge
}
fun main() {
println(age)
println(age)
}
// 计算年龄 11
// 11
// 11
lazy() 方法 也可以接受参数 ,默认为LazyThreadSafetyMode.SYNCHRONIZED
- LazyThreadSafetyMode.SYNCHRONIZED: 添加同步锁,使lazy延迟初始化线程安全
- LazyThreadSafetyMode. PUBLICATION: 初始化的lambda表达式可以在同一时间被多次调用,但是只有第一个返回的值作为初始化的值。
- LazyThreadSafetyMode. NONE:没有同步锁,多线程访问时候,初始化的值是未知的,非线程安全,一般情况下,不推荐使用这种方式,除非你能保证初始化和属性始终在同一个线程
可观察属性 observable / vetoable
如果你要观察一个属性的变化过程,那么可以将属性委托给Delegates.observable, observable函数原型如下:
var number: Int by Delegates.observable(1){
property, oldValue, newValue ->
println(" 属性名 : ${property.name}: 从 $oldValue -> $newValue ")
}
fun main() {
number = 2;
number = 3;
}
// 属性名 : number: 从 1 -> 2
// 属性名 : number: 从 2 -> 3
相比observable,vetoable 可以控制 新值的配置是否生效。假设一个人得年龄上限最大为100。
var age: Int by Delegates.vetoable(0) {
property, oldValue, newValue ->
newValue <= 100 //最后一行必须是逻辑
}
fun main() {
println(age)
age = 2;
println(age)
age = 101;
println(age)
}
// 0
// 2
// 2
可以看出,当年龄大于 100时,并未赋值成功
委托给另一个属性
一个属性可以把它的 getter 与 setter 委托给另一个属性。
-
顶层属性 var newValue: Int by ::value
-
同一个类的成员或扩展属性
var newValue: Int by this::newValue -
另一个类的成员或扩展属性
val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt委托属性时,查到的结果始终是最新值
属性存储在映射中
还有一种情况,在一个映射(map)里存储属性的值,使用映射实例自身作为委托来实现委托属性,如:
fun main() {
val user = User(mapOf(
"name" to "guoxu",
"age" to 28
))
println("name=${user.name} age=${user.age}")
}
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
注意:map的 key 和 属性名, value 和属性的数据格式 必须对应,否则报错