- 一、什么是委托?
- 二、使用场景
- 三、属性委托
- 四、Kotlin 标准库中提供几个委托
委托,也就是委托模式,它是23种经典设计模式种的一种,又名代理模式,在委托模式中,有2个对象参与同一个请求的处理,接受请求的对象将请求委托给另一个对象来处理。委托模式是一项技巧,其他的几种设计模式如:策略模式、状态模式和访问者模式都是委托模式的具体场景应用。
委托模式中,有三个角色,约束、委托对象和被委托对象。 解释说明:
-
约束: 约束是接口或者抽象类,它定义了通用的业务类型,也就是需要被代理的业务
-
被委托对象: 具体的业务逻辑执行者
-
委托对象: 负责对真是角色的应用,将约束累定义的业务委托给具体的委托对象。
现在很多年轻人都爱完游戏,不管是吃鸡、王者荣耀还是英雄联盟。它们都是有等级之分的:青铜->白银->黄金->铂金->钻石->宗师->王者,等级越高,代表你越厉害,就拿英雄联盟来说,我们多数混迹在白银黄金阶段,要上钻石宗师段位非常困难。比如你排位打了很久,就差几场就能上宗师了,老是打不上去,这个时候怎么办呢?好办,现在有很多游戏代练,委托游戏代练给你打上去就好了。这其实就是一个委托模式。
首先,我们定义约束类,定义我们需要委托的业务,就拿这个场景来说,我们的业务就是打排位赛,升级。因此,定义个约束类(接口)IGamePlayer:
// 约束类
interface IGamePlayer {
// 打排位赛
fun rank()
// 升级
fun upgrade()
}
约束类中,定义了我们要代理的业务rank(),upgrade(),然后,我们就定义被委托对象,也就是游戏代练:
// 被委托对象,本场景中的游戏代练
class RealGamePlayer(private val name: String): IGamePlayer{
override fun rank() {
println("$name 开始排位赛")
}
override fun upgrade() {
println("$name 升级了")
}
}
如上,我们定义了一个被委托对象RealGamePlayer, 它有一个属性name,它实现了我们约定的业务(实现了接口方法)。
接下来,就是委托角色:
// 委托对象
class DelegateGamePlayer(private val player: IGamePlayer): IGamePlayer by player
我们定义了一个委托类 DelegateGamePlayer
, 现在游戏代练有很多,水平有高有低,如果发现水平不行,我们可以随时换,因此,我们把被委托对象作为委托对象的属性,通过构造方法传进去。
注意:在 Kotlin 中,委托用关键字 by 修饰,by 后面就是你委托的对象,可以是一个表达式。因此在本例中,通过 by player 委托给了具体的被委托对象。
测试类:
// Client 场景测试
fun main() {
val realGamePlayer = RealGamePlayer("张三")
val delegateGamePlayer = DelegateGamePlayer(realGamePlayer)
delegateGamePlayer.rank()
delegateGamePlayer.upgrade()
}
运行结果:
张三 开始排位赛 张三 升级了
三、属性委托在Kotlin 中,有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们,但是很麻烦,各种样板代码存在,我们知道,Kotlin可是宣称要实现零样板代码的。为了解决这些问题呢?Kotlin标准为我们提供了委托属性。
class Test {
// 属性委托
var prop: String by Delegate()
}
委托属性的语法如下:
val/var : by
- 属性委托的原理
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
其中的参数解释如下:
thisRef
—— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;property
—— 必须是类型 KProperty或其超类型。value
—— 必须与属性同类型或者是它的子类型。
测试:
fun main() {
println(Test().prop)
Test().prop = "Hello, Android 技术!"
}
输出结果:
Test@5197848c, thank you for delegating ‘prop’ to me! Hello, Android技术杂货铺! has been assigned to ‘prop’ in Test@17f052a3.
- 另一种实现属性委托的方式 Kotlin 标准库中声明了2个含所需 operator方法的
ReadOnlyProperty / ReadWriteProperty
接口。
interface ReadOnlyProperty {
operator fun getValue(thisRef: R, property: KProperty): T
}
interface ReadWriteProperty {
operator fun getValue(thisRef: R, property: KProperty): T
operator fun setValue(thisRef: R, property: KProperty, value: T)
}
被委托类 实现这两个接口其中之一就可以了,val 属性实现 ReadOnlyProperty
,var 属性实现ReadOnlyProperty
。
// val 属性委托实现
class Delegate1: ReadOnlyProperty{
override fun getValue(thisRef: Any, property: KProperty): String {
return "通过实现ReadOnlyProperty实现,name:${property.name}"
}
}
// var 属性委托实现
class Delegate2: ReadWriteProperty{
override fun getValue(thisRef: Any, property: KProperty): Int {
return 20
}
override fun setValue(thisRef: Any, property: KProperty, value: Int) {
println("委托属性为: ${property.name} 委托值为: $value")
}
}
// 测试
class Test {
// 属性委托
val d1: String by Delegate1()
var d2: Int by Delegate2()
}
测试代码如下:
val test = Test()
println(test.d1)
println(test.d2)
test.d2 = 100
输出结果:
通过实现ReadOnlyProperty实现,name:d1 20 委托属性为: d2 委托值为: 100
四、Kotlin 标准库中提供几个委托Kotlin 标准库中提供了几种委托,例如:
- 延迟属性(lazy properties): 其值只在首次访问时计算;
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
4.1 延迟属性 lazy lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
val lazyProp: String by lazy {
println("Hello,第一次调用才会执行我!")
"西哥!"
}
// 打印lazyProp 3次,查看结果
fun main() {
println(lazyProp)
println(lazyProp)
println(lazyProp)
}
输出结果:
Hello,第一次调用才会执行我! 西哥! 西哥! 西哥!
4.2 可观察属性 Observable 如果你要观察一个属性的变化过程,那么可以将属性委托给Delegates.observable, observable
函数原型如下:
public inline fun observable(initialValue: T, crossinline onChange: (property: KProperty, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty =
object : ObservableProperty(initialValue) {
override fun afterChange(property: KProperty, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
参数:
initialValue
: 初始值onChange
: 属性值被修改时的回调处理器,回调有三个参数property,oldValue,newValue
,分别为: 被赋值的属性、旧值与新值。
使用:
var observableProp: String by Delegates.observable("默认值:xxx"){
property, oldValue, newValue ->
println("property: $property: $oldValue -> $newValue ")
}
// 测试
fun main() {
observableProp = "第一次修改值"
observableProp = "第二次修改值"
}
输出结果:
property: var observableProp: kotlin.String: 默认值:xxx -> 第一次修改值 property: var observableProp: kotlin.String: 第一次修改值 -> 第二次修改值
4.3 属性存储在映射中 还有一种情况,在一个映射(map)里存储属性的值,使用映射实例自身作为委托来实现委托属性,如:
class User(val map: Map) {
val name: String by map
val age: Int by map
}
测试如下:
fun main() {
val user = User(mapOf(
"name" to "西哥",
"age" to 25
))
println("name=${user.name} age=${user.age}")
}
输出结果:
name=西哥 age=25
使用映射实例自身作为委托来实现委托属性,可以使用在 json 解析中,因为 json 本身就可以解析成一个 map 。