IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> kotlin中的委托 -> 正文阅读

[移动开发]kotlin中的委托

理解委托

委托是一种设计模式,具体的操作不用自己实现,而是把操作委托给另一个辅助的对象,我们把这个辅助对象称为委托。

注:本篇博客内容来自《Kotlin实战》一书,经过自己的消化与学习整理的。

类委托

类委托,委托和代理似乎是在讲同一个东西,比如,一个老板想喝咖啡,老板嘛,比较懒,不想自己去买,于是他叫经理给他买,经理又叫他的一个手下去买。在这个关系中,经理可以理解为代理,代老板买东西,但是经理也没有直接去买,而是把工作交给了他的手下去买,这就可以理解成委托,委托他的手下去买。

我们可以这样理解,经理是一个代理,他可以做很多的事情,但是真正做事情的时候是委托给他的手去做的。

在代码中写一个示例,我们写一个集合的代理类,这个代理可以完成集合的各种操作,但是它是委托给ArrayList来完成的,如下:

class DelegatingCollection<T> : Collection<T> {
    
    private val innerList = ArrayList<T>()
    override val size: Int get() = innerList.size
    override fun contains(element: T): Boolean = innerList.contains(element)
    override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)
    override fun isEmpty(): Boolean = innerList.isEmpty()
    override fun iterator(): Iterator<T> = innerList.iterator()
    
}

如上代码,DelegatingCollection是一个集合代理类,它的功能和ArrayList一模一样,因为它的功能都是委托ArrayList来完成的,那我们为什么不直接使用ArrayList而要使用代理类呢?使用代理类我们是可以做一些其他事情的,比如在代理中打印代理函数的执行时间、调用次数等。

写一个代理类还是比较容易的,但是如果方法很多,这样写起来也是有点烦人的,通过Kotlin的类委托就可以解决这个烦恼,如下:

class DelegatingCollection<T>(innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList

Ok,就是这么清爽,这就像Kotlin中的data class一样,自动为你生成需要的方法。

我们也可以覆盖掉某些方法,自己实现,如下:

class DelegatingCollection<T>(val innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList {
    
    private var invokeCount = 0
    
    override fun isEmpty(): Boolean {
        invokeCount++
        return innerList.isEmpty()
    }
}

如上代码,我们覆盖了isEmpty()函数,在其中计算该函数被调用的次数,虽然最后也是委拖给innerList来完成isEmpty的判断,但是在其他场合,你完全可以使用自己的实现而不进行委托,比如经理,他的很多工作是委托给他的手下去完成的,但是有一些工作他不想委托给他的手,那他就自己实现了罗,比如泡妞自己实现就比较好_

属性委托

属性委托是把一个属性的访问器(即setter和getter)的逻辑委托给一个辅助对象。示例如下:

class Foo {
    var p: Type
}

如上代码,这是一个简单的kotlin类,有一个属性p,我们以可以自定义该属性的setter和getter,如下:

class Foo {
    var p: Type
        set(value: Type) { ... }
        get() = ...
}

如上代码完成了setter和getter的自定义,这些都是kotlin基础就有讲的。然后,我们可以把setter和getter的工作交给一个代理类来完成,如下:

class Foo {
    private val delegate = Delegate()
    var p: Type
        set(value: Type) = delegate.setValue(..., value)
        get() = delegate.getValue(...)
}

如上代码,调用属性p的setter和getter的具体实现逻辑交给delegate的setVaule和getValue来完成(setVaule仅适用于可变属性),setValue和getValue可以是成员函数,也可以是扩展函数。Delegate的简单实现如下:

class Delegate {
    operator  fun getValue(...) { ... }
    operator  fun setValue(..., value: Type) { ... }
}

按照Kotlin的约定,属性的委托类必须要有getValue和setValue方法(setVaule仅适用于可变属性),方法的具体参数后面会讲解。只要符合这个约定,我们就可以使用Kotlin的简洁语法完成属性委托,如下:

class Foo {
	var p: Type by Delegate 
}

fun main() {
	val foo = Foo()
	val oldValue = foo.p // 调用的是delegate.getValue(...)
	foo.p = newValue	 // 调用的是delegate.setValue(..., newValue)
}

使用属性委托来实现惰性初始化

惰性初始化是一种常见的模式,比如在设计单例的时候可以设计为懒加载的单例,即单例的初始化是惰性初始化的,用到的时候才把单例实例化,没有到的时候为null。

示例:一个Person有emails属性,保存了此人的所有邮件,当我们不访问这个属性的时候,它为null,当我们访问它的时候,如果为null就进行加载emails的操作(耗时操作),加载完成后emails就不为空了,当再次访问emails属性时,它就不会再执行加载emails的操作了,因为之前已经加载好了,这就是惰性初始化,不需要的时候为null,需要的时候才初始化,而且只初始化一次。示例代码如下:

class Person(val name: String) {
    
    private var _emails: List<Email>? = null
    
    val emails: List<Email>
        get() {
            if (_emails == null) {
                _emails = loadEmails(this)
            }
            return _emails
        }
        
}

这里使用了所谓的支持属性技术:即一个属性,用两个字段来完成,一个是_emails,用来存储这个属性的值,另一个是emails,用来提供对属性的读取。你需要两个属性来完成,因为他们具有不同的类型:_emails可以为空,而emails为非空,这种技术经常会使用到,值得熟练掌握。

但这个代码有点繁瑣,如果需要多个这样的惰性属性,那这个类就很臃肿了。而且,它并不总是正常运行:这个实现不是线程安全的,Kotlin提供了更好的解决方案,使用委托属性会让代码变得简单得多,通过lazy函数来返回委托的属性,lazy是一个标准的库函数,示例如下:

class Person(val name: String) {
	val emails by lazy{ loadEmails(this) }
}

lazy函数返回一个对象,该对象具有一个名为getValue且签名正确的方法,因此可以把它与by关键字一起使用来创建一个委托属性。lazy函数的参数是一个lambda,可以调用它来初始化这个值。默认情况下,lazy函数是线程安全的,如果需要,可以设置其他选项来告诉它要使用哪个锁,或者完全避开同步,如果该类永远不会在多线程环境中使用。

要了解委托属性的实现方式,让我们来看另一个例子:当一个对象的属性更改时通知监听器,在这许多不同的情况下都很有用,例如:当对象显示在UI时,你希望在对象变化时UI能自动刷新。Java具有用于此类通知的标准机制:PropertyChangeSupport和PropertyChangeEvent类。让我们看看在Kotlin中不使用委托属性的情况下,该如何使用它们,然后我们再将代码重构为用委托属性的方式。

PropertyChangeSupport类维护了一个监听器列表,并向它们发送PropertyChangeEvent事件。要使用它,你通常需要把这个类的一个实例存储为bean类的一个字段,并将属性更改的处理委托给它。

为了避免要在每个类中去添加这个字段,你需要创建一个小的工作类,用来存储PropertyChangeSupport的实例并监听属性更改。之后,你的类会继承这个工具类,以访问changeSupport,如下:

open class PropertyChangeAware {
    
    protected val changeSupport = PropertyChangeSupport(this)
    
    fun addPropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.addPropertyChangeListener(listener)
    }

    fun removePropertyChangeListener(listener: PropertyChangeListener) {
        changeSupport.removePropertyChangeListener(listener)
    }
    
}

现在我们来写一个Person类,定义一个只读属性(作为一个人的名称,一般不会随时更改)和两个可写属性:年龄和工资,当这个人的年龄或工资发生变化 时,这个类将通知它的监听器。代码如下:

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {

    var age: Int = age
        set(newValue) {
            val oldValue = field
            field = newValue
            changeSupport.firePropertyChange("age", oldValue, newValue)
        }

    var salary: Int = salary
        set(newValue) {
            val oldValue = field
            field = newValue
            changeSupport.firePropertyChange("salary", oldValue, newValue)
        }
}

fun main() {
    val p = Person("Dmitry", 34, 2000)
    p.addPropertyChangeListener(
        PropertyChangeListener { event ->
            println("Property ${event.propertyName} changed from ${event.oldValue} to ${event.newValue}")
        }
    )
    p.age = 35      // 输出:Property age changed from 34 to 35
    p.salary = 2100 // 输出:Property salary changed from 2000 to 2100
}

setter中有很多重复的代码,我们把它提取到一个类,如下:

class ObservableProperty(
    val propName: String,
    var propValue: Int,
    val changeSupport: PropertyChangeSupport
) {

    fun getValue(): Int = propValue

    fun setValue(newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(propName, oldValue, newValue)
    }

}

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {

    val _age = ObservableProperty("age", age, changeSupport)
    var age: Int
        get() = _age.getValue()
        set(newValue) = _age.setValue(newValue)

    val _salary = ObservableProperty("salary", age, changeSupport)
    var salary: Int
        get() = _salary.getValue()
        set(newValue) = _salary.setValue(newValue)
}

fun main() {
    val p = Person("Dmitry", 34, 2000)
    p.addPropertyChangeListener(
        PropertyChangeListener { event ->
            println("Property ${event.propertyName} changed from ${event.oldValue} to ${event.newValue}")
        }
    )
    p.age = 35      // 输出:Property age changed from 34 to 35
    p.salary = 2100 // 输出:Property salary changed from 2000 to 2100
}

现在,你应该已经差不多理解了在Kotlin中,委托属性是如何工作的。你创建了一个保存属性值的类,并在修改属性时自动触发更改通知。你删除了重复的逻辑代码,但是需要相当多的样板代码来为每个属性创建ObservableProperty实例,并把getter和setter委托给它。Kotlin的委托属性功能可以让你摆脱这些样板代码。但是在此之前,你需要更改ObservableProperty方法的签名,以符合Kotlin的约定方法。

class ObservableProperty(
    var propValue: Int,
    val changeSupport: PropertyChangeSupport
) {

    operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue

    operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {
        val oldValue = propValue
        propValue = newValue
        changeSupport.firePropertyChange(prop.name, oldValue, newValue)
    }

}

与之前的版本相比,这次代码做了一些更改:

  • 现在,按照约定的需要,getValue和setValue函数被标志了operator。
  • 这些函数加了两个参数:一个用于接收属性的实现,用来设置或读取属性,另一个用于表示 属性本身。这个属性类型为KProperty。
  • 把name属性从主构造方法中删除了,因为现在可以通过KProperty访问属性名称了。

终于,你可以见识Kotlin委托属性的神奇了,如下:

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
    var age: Int by ObservableProperty(age, changeSupport)
    var salary: Int by ObservableProperty(salary, changeSupport)
}

通过关键字by,Kotlin编译器会自动执行之前版本的代码中手动完成的操作。如果把这份代码与以前版本的Person类进行比较:使用委托属性时生成的代码非常类型。右边的对象被称为委托。Kotlin会自动将委托存储在隐藏的属性中,并在访问或修改属性时调用委托的getValue和setValue。

你不用手动去实现可观察的属性逻辑,可以使用Kotlin标准库,它已经包含了类似于ObservableProperty的类。标准库和这里使用的PropertyChangeSupport类没有耦合,因此你需要传递一个lambda,来告诉它如何通知属性值的更改。可以这样做:

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
    private val observer = { prop: KProperty<*>, oldValue: Int, newValue: Int ->
        changeSupport.firePropertyChange(prop.name, oldValue, newValue)
    }
    var age: Int by Delegates.observable(age, observer)
    var salary: Int by Delegates.observable(salary, observer)
}

by右边的表达式不一定是新创建的实例,也可以是函数调用、另一个属性或任何其他表达式,只要这个表达式的值能够被编译器用正确的参数类型来调用getValue和setValue的对象。与其他约定一样,getValue和setValue可以是对象自己声明的方法或扩展函数。

注意:为了让示例保持简单,我们只展示了如何使用类型为Int的委托属性,委托属性机制其实是通用的,适用于任何其他类型。

委托属性的变换规则

让我们来总结一下委托属性是怎么荼的,假设你已经有了一个具有委托属性的类:

class C {
    var prop: Type by MyDelegate()
}
val c = C()

MyDelegate实例会被保存到一个隐藏的属性中,它被称为:<delegate>,编译器也将用一个KProperty类型的对象来代表这个属性,它被称为:<property>。编译器生成的代码如下:

class C {
    private val <delegate> = MyDelegate()
    var prop: Type
        get() = <delegate>.getValue(this, <property>)
        set(value: Type) = <delegate>.setValue(this, <property>, value)
}

因为,在每个属性访问器中,编译器都会生成对应的getValue和setValue方法,如下:

val x = c.prop => val x = <delegate>.getValue(c, <property>)
c.prop = x => <delegate>.setValue(c, <property>, x)

这个机制非常简单,但它可以实现许多有趣的场景。你可以自定义存储该属性值的位置(map、数据库表或者用户会话的Cookie中),以及在访问该属性时做点什么(比如添加验证、更改通知等)。所有这一切都可以用紧凑的代码完成。我们再来看看标准库中委托属性的另一个用法,然后看看如何在自己的框架中使用它们。

在map中保存属性值
委托属性发挥作用的另一种常见用法,是用在有动态定义的属性集的对象中。这样的对象有时被称为自订(expando)对象。例如,考虑一个联系人管理系统,可以用来存储有关联系人的任意信息。系统 中的每个人都有一些属性需要特殊处理(例如名字),以及每个人特有的数量任意的额外属性(例如,最小的孩子的生日)。

实现这种系统的一种方法是将人的所有属性存储在map中,不确定提供属性,来访问需要特殊处理的信息。来看个例子:

class Person {
    private val _attributes = hashMapOf<String, String>()
    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }
    val name: String
        get() = _attributes["name"]!!
}

fun main() {
    val p = Person()
    val data = mapOf("name" to "Dmitry", "company" to "JetBrains")
    for ((attrName, value) in data) {
        p.setAttribute(attrName, value)
    }
    println(p.name) // 输出:Dmitry
}

这里使用了一个通用的API来把数据加载到对象中(在实际项目中,可以是JSON反序列化或类似的方法),然后使用特定的API来访问一个属性的值。把它改为委托属性非常简单,可以直接将map放在by关键字后面,如下:

class Person {
    private val _attributes = hashMapOf<String, String>()
    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }
    val name: String by _attributes
}

因为标准库已经在标准Map和MutableMap接口上定义了getValue和setValue扩展函数,所以这里可以直接这样用。属性的名称将自动用作在map中的键,属性值作为map中的值。

框架中的委托属性

更改存储和修改属性的方式对框架的开发人员非常有用。假设数据库中Users的表包含两列数据:字符串类型的name和整形的age。可以在Kotlin中定义Users和User类。在Kotlin代码中,所有存储在数据库中的用户实体的加载和更改都可以通过User类的实例来操作。

object Users : IdTable() {
    val name = varchar("name", length = 50).index()
    val age = integer("age")
}

class User(id: EntityID) : Entity(id) {
    var name: String by Users.name
    var age: Int by Users.age
}

Users对象描述数据库表的一个表:它被声明为一个对象,因为它对应整个表,所以只需要一个实例。对象的属性表示数据表的列。

User类的基类Entity,包含了实体的数据库列与值的映射。特定User的属性拥有这个用户在数据库中指定的值name和age。

框架用起来会特别方便,因为访问属性会自动从Entity类的映射中检索相应的值,而修改过的对象会被标记成脏数据,在需要时可将其保存到数据库中。可以在Kotlin代码中编写user.age += 1,数据库中的相应实体将自动更新。

现在你已经充分了解了如何实现具有这种API的框架。每个实体属性(name, age)都实现为委托属性,使用对象(Users.name, Users.age)作为委托:

class User(id: EntityID) : Entity(id) {
    var name: String by Users.name
    var age: Int by Users.age
}

让我们来看看怎样显式地指定列的类型:

object Users : IdTable() {
    val name: Column<String> = varchar("name", length = 50).index()
    val age: Column<Int> = integer("age")
}

至于Column类,框架已经定义了getValue和setValue方法,满足Kotlin的委托约定:

operator fun <T> Column<T>.getValue(o: Entity, desc: KProperty<*>): T {
    // 从数据库中获取值
}

operator fun <T> Column<T>.setValue(o: Entity, desc: KProperty<*>, value: T): T {
    // 更新值到数据库
}

可以使用Column属性(Users.name)作为被委托属性(name)的委托。当在代码中写入user.age += 1时,代码的执行将类似于user.ageDelegate.setValue(user.ageDelegate.getValue() + 1) (省略了属性和对象实例的参数)的操作。getValue和setValue方法负债检索和更新数据库中的信息。

总结:

  • 委托属性可以用来重用逻辑,这些逻辑控制如何存储、初始化、访问和修改属性值,这是用来构建框架的一个强大的工具。
  • lazy标准库函数提供了一种实现惰性初始化属性的简单方法
  • Delegate.observable函数可以用来添加属性更改的观察者。
  • 委托属性可以使用任意map来作为属性委托,来灵活来处理具有可变属性集的对象。
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-26 12:13:18  更:2021-08-26 12:14:22 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:49:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码