您当前的位置: 首页 >  kotlin

Kevin-Dev

暂无认证

  • 0浏览

    0关注

    544博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【Kotlin -- 知识点】Kotlin 中的委托

Kevin-Dev 发布时间:2022-02-22 09:50:35 ,浏览量:0

文章目录
      • 一、什么是委托?
      • 二、使用场景
      • 三、属性委托
      • 四、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 。

关注
打赏
1658837700
查看更多评论
立即登录/注册

微信扫码登录

0.0406s