原文地址:Kotlin学习快速入门(8)—— 属性委托 - Stars-One的杂货小窝

委托其实是一种设计模式,但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懒加载的方式,其实就是单例模式

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")
}
}

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

参考

Kotlin学习快速入门(8)—— 委托的更多相关文章

  1. Kotlin学习快速入门(7)——扩展的妙用

    原文地址: Kotlin学习快速入门(7)--扩展的妙用 - Stars-One的杂货小窝 之前也模模糊糊地在用这个功能,也是十分方便,可以不用继承,快速给某个类增加新的方法,本篇便是来讲解下Kotl ...

  2. Kotlin学习快速入门(3)——类 继承 接口

    类 参考链接 类定义格式 使用class关键字定义,格式如下: class T{ //属性 //构造函数 //函数 //内部类 } Java Bean类 java bean类 //java bean类 ...

  3. Kotlin学习快速入门(4)——集合使用

    List,Set,Map都是集合 List 是一个有序集合,可通过索引(反映元素位置的整数)访问元素.元素可以在 list 中出现多次.列表的一个示例是一句话:有一组字.这些字的顺序很重要并且字可以重 ...

  4. Kotlin学习快速入门(1)——基本数据类型以及String常用方法使用

    本文适合有Java基础的人 Kotlin语法特点 相比java,省略括号,可以自动判断类型,省略new关键字,空指针捕获 主函数 kotlin文件(kt文件)中,只有要下列的方法,就可以运行,无需像之 ...

  5. Kotlin学习快速入门(2)——条件 数组 循环 方法

    条件 if条件判断 常用的判断和Java一样,这里提一下不同的用法 1.if可以作为三元运算符 val max = if (a > b) a else b 2.使用in判断是否在某个区间 val ...

  6. Kotlin学习快速入门(5)——空安全

    介绍 kotlin中,对象可分为两种类型,可为空的对象和不可为空对象 默认为不可为空对象,代码检测如果发现不可为空对象赋予了null,则会标红报错. 可为空的对象,如果调用了方法,代码检测也会标红报错 ...

  7. pytest学习--快速入门

    一.pytest简介 Pytest是python的一种单元测试框架. pytest的特点: 入门简单,文档丰富 支持单元测试,功能测试 支持参数化,重复执行,部分执行,测试跳过 兼容其他测试框架(no ...

  8. pytorch入门--土堆深度学习快速入门教程

    工具函数 dir函数,让我们直到工具箱,以及工具箱中的分隔区有什么东西 help函数,让我们直到每个工具是如何使用的,工具的使用方法 示例:在pycharm的console环境,输入 import t ...

  9. Html5 学习系列(五)Canvas绘图API快速入门(1)

    引言:Canvas绘图API快速入门 在接触HTML5的初学者包括我都在很多地方见到非常炫的一些页面,甚至好多学习HTML5的开发者都是冲着Web端的页游去的,那么HTML5那么绚丽的页面效果以及游戏 ...

  10. Gradle学习系列之一——Gradle快速入门

    这是一个关于Gradle的学习系列,其中包含以下文章: Gradle快速入门 创建Task的多种方法 读懂Gradle语法 增量式构建 自定义Property 使用java Plugin 依赖管理 构 ...

随机推荐

  1. 神经网络优化篇:详解测试时的 Batch Norm(Batch Norm at test time)

    Batch归一化将的数据以mini-batch的形式逐一处理,但在测试时,可能需要对每个样本逐一处理,来看一下怎样调整的网络来做到这一点. 回想一下,在训练时,这些就是用来执行Batch归一化的等式. ...

  2. 升腾C92 刷 OpenWrt 作旁路由设置 DNS 服务、扩容分区、设置 swap

    最新博客文章链接 文字更新时间:2024/02/04 一直知道 OpenWrt 经常拿来做软路由软件.最近买了个二手小主机升腾 C92 来做旁路由服务器,其被归为瘦客户机一类,感觉和工控机差不多,现价 ...

  3. springboot log 没落盘

    在配置文件中增加了下面的配置并不起作用. logging.file=/xx/xx.log 是因为在 springboot 2.2 版本之后上面的采纳数已经被废弃,需要用下面新的参数: logging. ...

  4. sensitive-word 敏感词/脏词开源工具-v.0.10.0-脏词分类标签支持

    sensitive-word sensitive-word 基于 DFA 算法实现的高性能敏感词工具. 创作目的 实现一款好用敏感词工具. 基于 DFA 算法实现,目前敏感词库内容收录 6W+(源文件 ...

  5. GCC Arm 11.3rel1, 12.2编译提示 _close is not implemented and will always fail

    使用GCC Arm工具链开发的项目, 在11.2下编译正常, 但是升级到 arm-gnu-toolchain-11.3.rel1 以及 arm-gnu-toolchain-12.2 之后, 编译出现警 ...

  6. 升级 MDK 5.37 后的问题处理: AC6编译选项, printf, 重启失效等

    烧录后 Reset And Run 重启失效 存在于 MDK ARM 5.28 之后包括 5.37 的版本. 这些版本即使勾选 Reset And Run, 在烧录后也不会自动重启执行 需要做以下设置 ...

  7. 立创EDA使用笔记

    立创EDA 在立创EDA上制作的PCB可以直接导入到嘉立创下单, 每个注册用户每个月可以下两个免费订单, 限制PCB尺寸10CMx10CM, 每单五片, 连制作到快递全部免费, 非常适合做样板. 立创 ...

  8. Centos7虚拟机安装后的环境配置工作

    虚拟机安装配置 选择[基础设施服务器],勾选:开发工具和调试工具 关闭防火墙 systemctl stop firewalld.service systemctl disable firewalld. ...

  9. ORA-39087: Directory Name Is Invalid

    说明 有时我们在Oracle数据库服务器执行expdp/impdp过程中会碰到这个错误:ORA-39087: Directory Name Is Invalid,意思是我们指定的directory参数 ...

  10. Apipost参数描述的填写和参数描述库的使用

    请求参数的描述填写 对于header.query以及form-data和urlencode的body参数,我们在如下地方填写参数描述: 如图中所示,对于一个填写过的参数,我们可以在新建接口可以通过点击 ...