一、委托
委托设计模式是一个很好的实现继承的替代方案,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>
属性的get
和set
方法将会被委托给getValue
和setValue
方法
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
!!! 委托提供这部还时有些没有太理解,没太明白有什么非常的必要的使用场景,或其他方式实现不了的。