Kotlin进阶知识(五)——重用属性访问的逻辑:委托属性

493 阅读3分钟

引言:委托,一种设计模式,操作的对象不用自己执行,而是把工作委托给另一个辅助的对象,其中辅助对象称为**委托**。

一、委托属性的基本操作

委托属性的基本语法是这样的:

class Foo {
    var p: Type by Delegate()
}

属性p将他的访问器逻辑委托给了另一个对象:这里的Delegate类的一个新的实例。通过关键字**by对其后的表达式求值来获取这个对象,关键字by可以用于任何符合属性委托约定规则**的对象。

// 测试
class Foo {
    // 编译器会自动生成一个辅助属性
    private val delegate = Delegate()
    
    // “p”的访问都会调用对应的“delegate”的getValue和setValue方法
    var P: Int
        set(value: Int) = delegate.setValue(..., value)
        get() = delegate.getValue()
}

按照约定,Delegate类必须具有getValue和setValue方法(后者仅适用于可变属性)。

class Delegate {
    // getValue包含了实现getter的逻辑
    operator fungetValue(...) { ... }
    // setValue包含了实现setter的逻辑
    operator fun setValue(..., value: Type)
}

class Foo {
    // 关键字“by”把属性关联上委托对象。
    var p: Type by Delegate()
}

>>> val foo = Foo()
// 通过调用delegate.getValue(...)来实现属性的修改
>>> val oldValue = foo.p
// 通过调用delegate.setValue(..., newValue)来实现属性的修改。
>>> foo.p = newValue

二、使用委托属性:惰性初始化和“by lazy()”

惰性初始化是一种常见的模式,特别是在初始化过程消耗大量资源并且在使用对象时并不总是需要数据时特别有用。

  • 使用支持属性来实现惰性初始化
// 定义
class Email {/* ... */}
fun loadEmail(person: Person): List<Email> {
    println("Load emails for ${person.name}")
    return listOf(/*...*/)
}

class Person(val name: String) {
    // “emails”属性用来保存数据,关联委托
    private var _emails: List<Email>? = null
    val emails: List<Email>
        get() {
            if(_emails == null) {
                // 访问时加载文件
                _emails = loadEmail(this)
            }
            // 如果已经加载,就直接返回
            return _emails!!
        }
}

// 测试
>>> val person = Person("Alice")
// 第一次访问会加载邮件
>>> person.emails
Load emails for Alice
>>> person.emails

上述代码使用了支持属性技术,即一个属性,_emails,用来存储这个值,而另一个emails,用来提供对属性的读取访问。

为了缩减代码,Kotlin提供了标注库函数***lazy***返回的委托。

  • 用委托属性来实现惰性初始化
class Person(val name: String) {
   val emails by lazy { loadEmails(this) }
}

lazy函数返回一个对象,该对象具有一个名为getValue且签名正确的方法,因此可以把它与by关键字一起使用来创建一个委托属性。

lazy的参数是一个lambda,可以调用它来初始化这个值。默认情况下,lazy函数式线程安全的。

三、在map中保存属性值

自订对象:委托属性发挥作用的另一种常见用法,是用在有动态定义的属性集的对象中,这样的对象就称为自订对象。

  • 定义一个属性,把值存到map
// 定义
class Person {
    private val _attributes = hashMapOf<String, String>()
    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }

    val name: String
        // 从map手动检索属性
        get() = _attributes["name"]!!
}

// 测试
>>> val p = Person6()
>>> val data = mapOf("name" to "Dmitry", "company" to "JetBrains")
>>> for((attrName, value) in data)
        p.setAttribute(attrName, value)
>>> println(p.name)
Dmitry

上述代码中,p.name隐藏了 _attributes.getValue(p, prop)的调用,这里变为_attributes[prop.name]

  • 使用委托属性把值存到map中
class Person {
    private val _attributes = hashMapOf<String, String>()
    fun setAttribute(attrName: String, value: String) {
        _attributes[attrName] = value
    }

    // 把map作为委托属性
    val name: String by _attributes
}

上述代码中,属性的名称自动用作在map中的键,属性值作为map中的值。