注册

Kotlin学习快速入门—— 属性委托

委托其实是一种设计模式,但Kotlin把此特性编写进了语法中,可以方便开发者快速使用,本篇也来具体讲解下关于Kotlin中属性委托的使用


委托对应的关键字是by


属性委托


先讲下属性委托吧,首先,复习下kotlin中设置set和get方法


默认的set和get我们可以隐藏,实际上一个简单的类代码如下:


class Person {
var personName = ""
// 这是默认的 get/set(默认是隐藏的)
get() = field
set(value) {
field = value
}
}

这里具体知识点可以查看之前所说Kotlin学习快速入门(3)——类 继承 接口 - Stars-One的杂货小窝


当然,如果是数据bean类,我们会将get和set方法隐藏(或者使用data关键字来声明一个数据类)


若我们需要在get或set方法的时候做一下逻辑处理,比如说上面的personName字段,我们只允许接收长度小于等于10的字符串,超过10长度的字符串就不接收(即不设置新数值),则是应该这样写:


class Person{
var personName = ""
// 这是重写的 get/set
get() = "PersonName $field"
set(value) {
field = if (value.length <= 10) value else field
}
}

然后,我们再延伸出来,如果此规则不止应用于personName字段,还可用到其他类的字段中,这个时候就是使用到属性委托。



简单描述: 我们将此规则抽取出来,需要应用到此规则的字段的get/set方法委托给规则去做,这就叫属性委托



延迟加载(懒加载)


在开始讲属性委托之前,先说明下延迟加载


Kotlin中提供了lazy方法,使用by+lazy{}联用,我们就实现延迟加载(也可称作懒加载)


fun main() {

val demo = Demo()
val textContent = demo.textContent
val result = demo.textContent.substring(1)
println(result)
println("打印:$textContent")
}

class Demo{

val textContent by lazy { loadFile() }

}
fun loadFile(): String {
println("读取文件...")
//模拟读取文件返回数据
return "读取的数据"
}

这里的关键词by出现在属性名后面,表示属性委托,即将属性的读和写委托给另一个对象,被委托的对象必须满足一定的条件:



  • 对于 val 修饰的只读变量进行属性委托时,被委托的对象必须实现getValue()接口,即定义如何获取变量值。
  • 对于 var 修饰的读写变量进行属性委托时,被委托对象必须实现getValue()setValue()接口,即定义如何读写变量值。


lazy()方法,接收一个lambda函数,返回值是一个Lazy对象,所以就可以简写成上面的样子,其只实现了getValue()接口,所以,当你尝试将textContent改为var类型,IDE会提示报错!!



也是因为这点,属于延迟加载的字段,是不可被再次修改了,所以采用lazy懒加载的方式,其实就是单例模式



lazy函数默认是线程安全的,而且是通过加锁实现的。如果你的变量不会涉及到多线程,那么请务必使用LazyThreadSafetyMode.NONE参数,避免不必要的性能开销,如下示例代码



val name:String by lazy(LazyThreadSafetyMode.NONE) { "Karl" }

Delegates.vetoable


还记得上述我们要实现的规则吗,其实Kotlin中已经有了几个默认的委托规则供我们快速使用(上述的lazy其实也是一个)


Delegates.vetoable()的规则就是上述规则的通用封装,解释为:


但会在属性被赋新值生效之前会传递给Delegates.vetoable()进行处理,依据Delegates.vetoable()的返回的布尔值判断要不要赋新值。


如下面例子:


class Person {
var personName by Delegates.vetoable("") { property, oldValue, newValue ->
//当设置的新值满足条件,则会设置为新值
newValue.length <= 10
}
}

Delegates.notNull


设置字段不能为null,不过想不到具体的应用情景


class Person {
var personName by Delegates.notNull<String>()
}

Delegates.observable


使用Delegates.observable可以帮我们快速实现观察者模式,只要字段数值发生改变,就会触发


class Person{
var age by Delegates.observable(0){ property, oldValue, newValue ->
//这里可以写相关的逻辑
if (newValue >= 18) {
tip = "已成年"
}else{
tip = "未成年"
}
}

var tip =""
}

上面的例子就比较简单,设置age同时更新提示,用来判断是否成年


 val person = Person()
person.age = 17
println(person.tip)

补充-自定义委托


上述都是官方定义好的一些情形,但如果不满足我们的需求,这就需要自定义委托了


官方提供了两个基础类供我们自定义委托使用:


ReadWriteProperty 包含get和set方法,对应var关键字
ReadOnlyProperty 只有get方法,对应val关键字



PS:实际上,我们自己随意创建个委托类也是可以的,不过这样写不太规范,所以我们一般直接实现官方给的上述两个类即可




ReadWriteProperty和ReadOnlyProperty都需要传两个泛型,分别为R,T



  • R 持有属性的类型
  • T 字段类型

可能上面描述不太明白,下面给个简单例子,Person类中有个name字段(String),首字母需要大写:


class Person {
var name by NameToUpperCase("")
}

class NameToUpperCase(var value:String) :ReadWriteProperty<Person,String>{
//NameToUpperCase类中默认有个属性字段,用来存数据

override fun getValue(thisRef: Person, property: KProperty<*>): String {
return this.value
}

override fun setValue(thisRef: Person, property: KProperty<*>, value: String) {
//在设置数值的时候,将第一个字母转为大写,一般推荐在setValue里编写逻辑
this.value = value.substring(0,1).toUpperCase()+value.substring(1)
}
}

个人看法,一般在setValue的时候进行设置数值比较好,因为getValue作操作的话,会触发多次,处理的逻辑复杂的话可能会浪费性能...


当然,再提醒下,上面的逻辑也可以直接去字段里的setValue()里面改,使用委托的目的就是方便抽取出去供其他类使用同样的规则,减少模板代码



PS: 如果你的委托不是针对特定的类,R泛型可以改为Any



类委托


这个一般与多态一起使用,不过个人想不到有什么具体的应用情景,暂时做下简单的记录


interface IDataStorage{
fun add()
fun del()
fun query()
}

class SqliteDataStorage :IDataStorage{
override fun add() {
println("SqliteDataStorage add")
}

override fun del() {
println("SqliteDataStorage del")
}

override fun query() {
println("SqliteDataStorage query")
}

}

假如现在我们有个MyDb类,查询的方法与SqliteDataStorage这个里的方法有所区别,但其他方法都是没有区别,这个时候就会用到类委托了


有以下几种委托的使用方式


1.委托类作为构造器形参传入(常用)


class MyDb(private val storage:IDataStorage) : IDataStorage by storage{
override fun add() {
println("mydb add")
}
}

val db = MyDb(SqliteDataStorage())
db.add()
db.query()

输出结果:


mydb add
SqliteDataStorage query

如果是全部都是委托给SqliteDataStorage的话,可以简写为这样:


class MyDb(private val storage:IDataStorage) : IDataStorage by storage

2.新建委托类对象


class MyDb : IDataStorage by SpDataStorage(){
override fun add() {
println("mydb add")
}
}

这里测试的效果与上文一样,不在重复赘述


参考



作者:Stars-One
链接:https://juejin.cn/post/7134886417934581768
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册