首页 Kotlin学习笔记(13) - 委托
文章
取消

Kotlin学习笔记(13) - 委托

一、委托

委托设计模式是一个很好的实现继承的替代方案,Kotlin能够原生的支持这种设计模式。委托模式可以通过 by 关键字在类中使用,也可以通过标准库中提供的几个委托类来简化一些常见的模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//EnhancedPrinter 类委托了 Printer 接口的实现给 standardPrinter 对象。
interface Printer {
    fun printMessage(message: String)
}

class StandardPrinter : Printer {
    override fun printMessage(message: String) {
        println(message)
    }
}

class EnhancedPrinter(delegate: Printer) : Printer by delegate

val standardPrinter = StandardPrinter()
val enhancedPrinter = EnhancedPrinter(standardPrinter)

enhancedPrinter.printMessage("Hello, Kotlin!")

二、覆盖由委托实现的接口成员

可以通过委托来覆盖已实现的方法逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Base {
    fun printMessage()
    fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
    override fun printMessage() { print(x) }
    override fun printMessageLine() { println(x) }
}

class Derived(b: Base) : Base by b {
    override fun printMessage() { print("abc") }
}

fun main() {
    val b = BaseImpl(10)
    Derived(b).printMessage() //print abc
    Derived(b).printMessageLine() //print 10
}

需要注意在覆盖时,基类中只会调用自己的成员属性,不会从委托对象中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Base {
    val message: String
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}

class Derived(b: Base) : Base by b {
    // This property is not accessed from b's implementation of `print`
    override val message = "Message of Derived"
}

fun main() {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print() //print BaseImpl: x = 10
    println(derived.message) // Message of Derived
}

三、属性委托

1
2
//语法格式
val/var <property name>: <Type> by <expression>

属性的getset方法将会被委托给getValuesetValue方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import kotlin.reflect.KProperty

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

class Example {
    var p: String by Delegate()
}

val e = Example()
println(e.p)

四、属性委托的一些标准方法

系统提供了一些用于委托的标准工厂方法

1. 延迟属性(Lazy properties)

接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托。 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果。 后续调用 get() 只是返回记录的结果

1
2
3
4
5
6
7
8
9
val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main() {
    println(lazyValue) // print computed! \n Hello
    println(lazyValue) //pirint Hello
}

2. 可观察属性(Observable properties)

Delegates.observable() 接受两个参数:初始值与修改时处理程序(handler)。

每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}

3. 委托给另一个属性

可以将一个属性委托给另一个属性,实际使用时例如对一个属性进行了重命名,向后兼容时可以考虑属性委托,将旧属性标记为@Deprecated 然后委托到到新属性上。

1
2
3
4
5
6
7
8
9
10
11
12
13
//对属性进行委托时,使用::限定符,eg.this::delegate  , MyClass::delegate
class MyClass {
   var newName: Int = 0
   @Deprecated("Use 'newName' instead", ReplaceWith("newName"))
   var oldName: Int by this::newName
}
fun main() {
   val myClass = MyClass()
   // 通知:'oldName: Int' is deprecated.
   // Use 'newName' instead
   myClass.oldName = 42
   println(myClass.newName) // 42
}

4. 属性存储在mapping中的委托

通常可能一些配置、开关等从json中解析后会在一个mapping中,可以使用委托进行映射

1
2
3
4
5
6
7
8
9
10
class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

5. 局部变量的属性委托

例如我们希望在执行某方法时先进行检测,如果检测不通过则不会初始化对象

1
2
3
4
5
6
7
fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

五、属性委托的规则

  • val声明的必须提供getValue()
  • var声明的必须提供getValue()setValue()
  • 函数中thisRef必须与属性所有者类型相同或是其父类
  • 函数中 property必须是KProperty<*> 或是其父类
  • 函数中 value 必须与属性类型相同或是其父类
  • 函数必须使用operator关键字修饰
  • 委托函数可以通过实现 ReadWriteProperty,ReadOnlyProperty来实现,更方便一些

六、属性委托的转换规则

在底层进行委托时编译器会为某些类型生成辅助属性,然后委托给他们

1
2
3
4
5
6
7
8
9
10
11
12
//例如,对于属性prop它生成隐藏属性prop$delegate,并且访问器的代码简单地委托给这个附加属性
class C {
    var prop: Type by MyDelegate()
}

// this code is generated by the compiler instead:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

当委托给另一个属性时会直接调用委托属性,跳过getValue,setValue运算,也不会有辅助属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class C<Type> {
    private var impl: Type = ...
    var prop: Type by ::impl
}

// this code is generated by the compiler instead:
class C<Type> {
    private var impl: Type = ...

    var prop: Type
        get() = impl
        set(value) {
            impl = value
        }

    fun getProp$delegate(): Type = impl // This method is needed only for reflection
}

七、委托提供

可以通过provideDelegate在属性委托前进行一个拦截,来做一些额外的事情。

provideDelegate方法仅会影响辅助属性的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // create delegate
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}

class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

可以直接使用标准库中的接口来完成委托提供,无需创建新类

1
2
3
4
val provider = PropertyDelegateProvider { thisRef: Any?, property ->
    ReadOnlyProperty<Any?, Int> {_, property -> 42 }
}
val delegate: Int by provider

!!! 委托提供这部还时有些没有太理解,没太明白有什么非常的必要的使用场景,或其他方式实现不了的。

本文由作者按照 CC BY 4.0 进行授权

Kotlin学习笔记(12) - 对象表达式与对象声明

Kotlin学习笔记(14) - 类型别名