委托模式是常见的设计模式之一。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。与Java一样,Kotlin也支持委托模式,通过关键字by。
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。例如下面的Java实例:
class RealPrinter { // the "delegate"
void print() {
System.out.print("something");
}
}
class Printer { // the "delegator"
RealPrinter p = new RealPrinter(); // create the delegate
void print() {
p.print(); // delegation
}
}
public class Main {
// to the outside world it looks like Printer actually prints.
public static void main(String[] args) {
Printer printer = new Printer();
printer.print();
}
}
可以看到在Java代码中printer 最终其实调用了RealPrinter的方法。用kotlin表示则需要用到by关键字:
// 创建接口
interface Base {
fun print()
}
// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b
fun main(args: Array) {
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b。
属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。
属性委托的具体语法格式如下:
val/var <属性名>: <类型> by <表达式>
· var/val:属性类型(可变/只读)
· 属性名:属性名称
· 类型:属性的数据类型
· 表达式:委托代理类
by 关键字之后的表达式就是委托, 属性的 get()和set() 方法将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口, 但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。
该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,prop 为进行委托的属性的对象。实例如下:
import kotlin.reflect.KProperty
// 定义包含属性委托的类,KProperty是个接口
class PropertyExample {
var str: String by Delegate()
}
// 委托的类
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 这里委托了 ${property.name} 属性"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("--- $thisRef 的 ${property.name} 属性赋值为 $value ---")
}
}
fun main(args: Array) {
val example = PropertyExample()
println(example.str) // 访问该属性,调用 Delegate.getValue()
example.str = "Google" // 调用 Delegate.setValue()
println(example.str)
}
对应的控制台输出结果为:
这里做一个简单的说明:
· thisRef:属性的拥有者;
· property:对属性的描述,是 KProperty<*> 类型或是它的父类;
· value:属性的值。
Kotlin的标准库提供很多工厂方法来实现属性的委托:
通过 lazy 我们可以定义一个懒加载的属性,该属性的初始化不会再类创建的时候发生,而是在第一次用到它的时候赋值。
lazy() 是一个函数, 是接受一个 Lambda 表达式作为参数, 返回一个 Lazy
下面是kotlin的经典示例:
val lazyValue: String by lazy {
println(" lazyValue print ") // 第一次调用输出,第二次调用不执行
"lazyValue print again"
}
fun main(args: Array) {
println(lazyValue) // 第一次执行,执行两次输出表达式
println(lazyValue) // 第二次执行,只输出返回值
}
对应的输出结果为:
observable,让属性在发生变动的时候可以被关注的地方观察到。可以用于实现观察者模式。
Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。
在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值:
import kotlin.properties.Delegates
class ObserveUser {
var name: String by Delegates.observable("初始值") {
prop, old, new ->
println("旧值:$old -> 新值:$new")
}
}
fun main(args: Array) {
val user = ObserveUser()
user.name = "第一次赋值"
user.name = "第二次赋值"
}
对应控制台输出为:
常见的用法是在一个映射(map)里存储属性的值。这种情况经常出现在像解析 JSON 或者做其他"动态"事情的应用中。这种情况下,可以使用映射实例自身作为委托来实现委托属性。
class WebSite(val map: MutableMap) {
val company: String by map
val url: String by map
}
fun main(args: Array) {
var map:MutableMap = mutableMapOf(
"company" to "谷歌大法好",
"url" to "www.Google.com"
)
val site = WebSite(map)
println(site.company)
println(site.url)
println("--------------")
map.put("company", "白度全广告")
map.put("url", "www.baiduu.com")
println(site.company)
println(site.url)
}
对应的输出结果为:
局部变量可以声明为委托属性。比如使用lazy初始化一个局部变量:
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
上述代码中,memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。
对于只读属性(val属性), 它的委托必须提供一个getValue()函数。该函数接受以下参数:
· thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型;
· property —— 必须是类型 KProperty<*> 或其超类型。
这个函数必须返回与属性相同的类型(或其子类型)。
对于一个值可变(mutable)属性(var属性),除getValue()函数之外,它的委托还必须再提供一个setValue()函数, 这个函数接受以下参数:
· property —— 必须是类型 KProperty<*> 或其超类型;
· new value —— 必须和属性同类型或者是它的超类型。