类代理(Class Delegation)
代理模式提供一种实现继承的替代方式,Kotlin原生就支持代理模块。
如下,“Derived
”继承“Base
”接口,并代理了它的全部公共方法:
[java] view plain copy
- interface Base {
- fun print()
- }
- class BaseImpl(val x: Int) : Base {
- override fun print() { print(x) }
- }
- class Derived(b: Base) : Base by b
- fun main(args: Array<String>) {
- val b = BaseImpl(10)
- Derived(b).print() // prints 10
- }
通过“by”关键字,将“b”实例存储到Derived对象中,编译器会生成“Base
”接口的所有方法,使用“b”的实现。
代理属性(Delegated Properties)
对于很多公用属性,尽管在每次需要的时候可以通过手动实现;更好的方式是一次实现多次使用,并放到一个库(library)。
比如,有下面类型的属性:
Ø 延迟属性(lazy properties):只有第一次访问时才会计算值。
Ø 观察属性(observable properties):当该属性发生改变时,会通知监听者。
Ø map中存储属性,不是在单独的字段中。
在Kotlin中,为了满足上面几种情况,提供了代理属性(delegated properties):
[java] view plain copy
- class Example {
- var p: String by Delegate()
- }
语法:val/var <property name>: <Type> by <expression>,“by”关键字后面的表达式就是代理(delegate);属性的“get()”和“set()”对应代理的“getValue()”和“setValue()”。属性代理不要去实现任何接口,但需要提供“getValue()”和“setValue()”(val属性不需要,var需要)函数。
如:
[java] view plain copy
- 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.'")
- }
- }
当读取“p”的值时,实际是访问它的代理“Delegate”实例的“getValue()”函数,第一个参数是读取“p”的实例(即对应的类的实例),第二个参数为“p”自身的描述。如:
[java] view plain copy
- val e = Example()
- println(e.p) //Example@33a17727, thank you for delegating ‘p’ to me!
同样,当给“p”赋值时,会调用代理的“setValue()”函数;前面两个参数跟“getValue()”函数一致,第三个参数为赋的值。如:
[java] view plain copy
- e.p = "NEW"
- //打印结果:
- //NEW has been assigned to ‘p’ in Example@33a17727.
属性代理的要求(Property Delegate Requirements)
下面总结代理属性的要求:
只读属性(read-only,使用val定义)
代理类提供“getValue”函数,参数要求:
Ø 接收者(receiver):第一个参数,必须是属性对应的类或父类型。
Ø 元数据(metadata):第二个参数,必须是“KProperty<*>”或它的父类型。
Ø 该函数,必须返回一个跟属性同类型的值。
可变属性(mutable,使用var定义)
代理类的“getValue”函数跟只读属性的一样;另外还需要提供一个“setValue”函数,参数要求:
Ø 接收者(receiver):第一个参数,同“getValue”对应的参数。
Ø 元数据(metadata):第二个参数,同“getValue”对应的参数。
Ø 新值:第三个参数,类型必须跟属性一样或其父类型。
“getValue”与“setValue”函数,可以是代理类的成员函数或扩展函数。当需要将一个对象作为一个代理属性的代理,而该类没有对应的“getValue”与“setValue”函数,通过扩展函数方式实现就非常方便。
另外两个函数需要使用“operator”关键字修饰。
标准代理(Standard Delegates)
Kotlin的标准库,通过工厂方法提供了一些有用的代理类。
Lazy
“lazy()
”函数接受Lambda表达式 并 返回“Lazy<T>”实例,它可以当做延迟属性的代理实现:当第一次属性执行“get()”(获取属性值,即使用该属性)时,会通过“lazy()”函数执行添加的Lambda表达式并记录返回值;后续再使用该属性时,直接使用记录的值。
[java] view plain copy
- val lazyValue: String by lazy {
- println("computed!")
- "Hello"
- }
- fun main(args: Array<String>) {
- println(lazyValue)
- println(lazyValue)
- }
- //prints:
- //computed!
- //Hello
- //Hello
默认情况,延迟属性的赋值是线程同步的:只会在一个线程中计算一次值,其他线程使用同一个值。如果初始化同步不是必须的,可以通过将“lazy()”函数的参数设置为“LazyThreadSafetyMode.PUBLICATION”,那么多个线程对其同时赋值。如果可以确保属性只会在单个线程中初始化,可以将“lazy()”设置“LazyThreadSafetyMode.NONE”模式,该模式下,不会确保线程安全及相关开销。
可观察方法(Observable)
“Delegates.observable()”,包含两个参数:初始化值和 属性值修改的回调handler;每次对属性赋值操作,都会回调该handler方法(在属性赋值后执行),该方法包含三个参数,分别为:属性对象,原值,新值。
[java] view plain copy
- class User {
- var name: String by Delegates.observable("<nomalName>") {
- prop, old, new ->
- println("$old -> $new")
- }
- }
- fun main(args: Array<String>) {
- val user = User()
- user.name = "first"
- user.name = "second"
- }
- //结果:
- //< nomalName > -> first
- //first -> second
另,如果需要拦截修改属性值动作并禁止修改,可以使用“Delegates.vetoable()”,参数跟“observable()”类似,第二个回调handler需要返回一个Boolean,true表示同意修改,false表示禁止修改;该回调会在属性值修改前调用。
[java] view plain copy
- class User {
- var name: String by Delegates. vetoable ("<nomalName>") {
- prop, old, new ->
- println("want modify $old -> $new")
- false
- }
- }
- fun main(args: Array<String>) {
- val user = User()
- println(user.name)
- user.name = "newValue"
- println(user.name)
- }
- //结果:
- //< nomalName >
- //want modify < nomalName > -> newValue
- //< nomalName >
Storing Properties in a Map
经常会在map中存储属性值,经常用于如解析JSON或其他“动态”事情。可以使用map对象作为一个代理属性的代理:
[java] view plain copy
- 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
- ))
- //
- println(user.name) // Prints "John Doe"
- println(user.age) // Prints 25
注:若属性定义为“var”,需要使用“MutableMap
”代替只读的map。