8.2类委托和委托属性
委托是一种设计模式,它的基本概念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。在Java中并没有委托,而像C#等语言就对委托进行了原生得支持。
8.2.1类委托
核心思想:将一个类的具体实现委托给另一个类去完成。
举一个例子:定义一个MySet,并让它实现Set接口:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
override val size: Int
get() = helperSet.size
override fun contains(element: T) = helperSet.contains(element)
override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)
override fun isEmpty() = helperSet.isEmpty()
override fun iterator() = helperSet.iterator()
}
可以看到,MySet的构造函数接收了一个HashSet参数,这就相当于一个辅助对象。然后在Set接口所有的方法实现中,我们都没有进行自己的实现,而是调用了辅助对象相应的方法实现,这就是一种委托模式。
这种写法有啥好处呢? 既然是调用辅助对象的方法实现,那不如直接使用辅助对象得了。但是我们想让部分方法实现由自己来重写,甚至加一些自己独有的方法,那么MySet就会成为一个全新的数据结构,这就是委托模式的意义所在。
但是存在一个问题,如果接口待实现的方法比较少那还行,如果有几十个甚至上百个方法话,那可不太妙。针对这个问题有什么解决方案呢?在Kotlin中可以通过类委托来解决。
Kotlin中委托使用的关键字是by,我们只需要在接口声明的后面使用by关键字,再接上受委托的辅助对象就可以免去之前所写的一大堆模板代码了。如下所示:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
}
借助类委托的功能之后,代码明显简化了很多,如果我们想对某个方法进行重写,只需要单独重现那一个方法就可以了,其它方法仍然可以享受类委托所带来的便利,如下所示:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
fun helloWorld() = println("Hello World!")
override fun isEmpty() = false
}
我们新增了一个新方法,并且重写了isEmpty()函数,虽然这是一种错误写法,但只是为了演示下而已。至于其他Set接口中的功能,则和HashSet保持一致。这就是Kotlin类委托所能实现的功能。
8.2.2委托属性
核心思想:将一个属性(字段)具体实现委托给另一个类去完成。
语法结构:
class MyClass{
var p by Delegate()
}
可以看到,这种写法代表将p属性的具体实现委托给了Delegate类去完成。
当调用p属性时会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法,所以我们必须实现这俩个函数,而且这俩个函数都需要使用operator关键字进行声明。
接着我们对Dletegate类进行具体实现:
class Delegate{
var propValue: Any? = null
operator fun getValue(myClass: MyClass,prop: KProperty<*>):Any?{
return propValue
}
operator fun setValue(myClass: MyClass,prop: KProperty<*>,value: Any?){
propValue = value
}
}
getValue,setValue第一个参数用于声明Delegate类得委托功能可以再什么类中使用。第二的参数KProperty<*>时Kotlin中一个属性操作类,可以用于获取各种属性相关的值。在当前场景下虽然用不着,但必须在方法参数上进行声明。另外<*>这种泛型的写法表示你不知道或者不关心泛型的具体类型,只是为了通过语法编译而已。而setValue第三个参数value表示具体要赋给委托属性的值,这个参数得类型和getValue方法的范围值类型保持一致。
要注意如果在MyClass中的p是val的话,该属性是无法在初始化后被重新赋值的,因此也没有必要实现setValue方法,只需要实现getValue方法即可。