实现一个接口,可以使用by关键字将接口实现委托给另一个对象。
interface OnClickListener{
fun onClick()
fun onLongClick()
}
class ViewClickDelegate : OnClickListener{
override fun onClick(){
println("ViewClickDelegate onClick")
}
override fun onLongClick() {
println("ViewClickDelegate onLongClick")
}
}
class View(val name: String, onClickListener: OnClickListener) : OnClickListener by onClickListener{
override fun onLongClick() {
println("$name onLongClick")
}
}
类委托后我们依然可以通过重写的方式来覆盖委托类的实现,这里View实现onLongClick方法,覆盖重写了ViewClickDelegate类里的onLongClick方法。
类委托的本质是:把抽象方法的实现交给了by后的委托对象
不在对象创建的时候初始化,而是在第一次使用时初始化。完成后像普通属性一样使用
open class Food(val name: String) {
override fun toString(): String {
return "[$name]"
}
}
class Container(val name: String) {
lateinit var foodList: List
}
第一次使用该属性时才初始化,且只初始化一次。用旗号标示是否初始化过,旗号有多种选择和实现方式。
在代码定义处执行初始化,有助于代码维护。
对指令式语言,这个模式可能潜藏着危险,尤其是使用共享状态的程式习惯。
class Container2(val name: String) {
private var _foodList: List? = null
val foodList: List
get() {
if (_foodList == null) {
_foodList = arrayListOf(Food("米糊"))
}
return _foodList!!
}
}
class Container4(val name: String) {
val food: Food by lazy{
Food("米糊")
}
}
//指定锁
class Container5(val name: String) {
val food: Food by lazy(Container5::class){
Food("米糊")
}
}
//默认 线程安全 SYNCHRONIZED
//PUBLICATION,同步锁不是必需的,允许多个线程同时执行
class Container6(val name: String) {
val food: Food by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
Food("米糊")
}
}
在JavaBean的设计中,按照属性的不同作用又细分为四类:单值属性,索引属性;关联属性,限制属性。接下来看下Kotlin如何实现关联属性和限制属性的
class Shelf(val name: String, _book: Book) {
private val propertyChange: PropertyChangeSupport = PropertyChangeSupport(this)
var book: Book = _book
set(value) {
val oldBook = field
field = value
propertyChange.firePropertyChange("book", oldBook, value)
}
fun addBookChangeListener(propertyChangeListener: PropertyChangeListener) {
propertyChange.addPropertyChangeListener("book", propertyChangeListener)
}
fun removeBookChangeListener(propertyChangeListener: PropertyChangeListener) {
propertyChange.removePropertyChangeListener("book", propertyChangeListener)
}
}
把逻辑封装,抽取出基类
open class BasePropertyChange {
val propertyChange = PropertyChangeSupport(this)
protected fun addChangeListener(key: String, propertyChangeListener: PropertyChangeListener) {
propertyChange.addPropertyChangeListener(key, propertyChangeListener)
}
protected fun removeChangeListener(key: String, propertyChangeListener: PropertyChangeListener) {
propertyChange.removePropertyChangeListener(key, propertyChangeListener)
}
}
class Shelf_2(val name: String, _book: Book) : BasePropertyChange() {
var book: Book = _book
set(value) {
val oldBook = field
field = value
propertyChange.firePropertyChange("book", oldBook, value)
}
fun addBookChangeListener(propertyChangeListener: PropertyChangeListener) {
addChangeListener("book", propertyChangeListener)
}
fun removeBookChangeListener(propertyChangeListener: PropertyChangeListener) {
removeChangeListener("book", propertyChangeListener)
}
}
把book里的set访问器的逻辑封装成一个类
class BookDelegate(_book: Book, val propertyChange: PropertyChangeSupport) {
var field: Book = _book
fun getValue(): Book = field
fun setValue(value: Book) {
val oldBook = field
field = value
propertyChange.firePropertyChange("book", oldBook, value)
}
}
class Shelf2(val name: String, _book: Book) : BasePropertyChange() {
val _bookDelegate: BookDelegate = BookDelegate(_book, propertyChange)
var book: Book
set(value) {
_bookDelegate.setValue(value)
}
get() = _bookDelegate.getValue()
fun addBookChangeListener(propertyChangeListener: PropertyChangeListener) {
addChangeListener("book", propertyChangeListener)
}
fun removeBookChangeListener(propertyChangeListener: PropertyChangeListener) {
removeChangeListener("book", propertyChangeListener)
}
}
至此,我们用Kotlin手工实现了可观察属性变化的功能,测试下
fun testObserverField() {
val shelf = Shelf2("书架", Book("Think in java"))
shelf.addBookChangeListener(object : PropertyChangeListener {
override fun propertyChange(evt: PropertyChangeEvent?) {
val oldBook = evt?.oldValue as Book
val newBook = evt.newValue as Book
println("old book = $oldBook , new book = $newBook")
}
})
shelf.book = Book("Kotlin in action")
}
运行上述代码结果如下:
old book = Book(name=Think in java) , new book = Book(name=Kotlin in action)
Kotlin的委托属性在语言层面提供了在属性的读访问器里调用委托类里operator修饰的两参数getValue方法,属性写访问器调用operator修饰setValue三个参数方法
class BookDelegate2(_book: Book, val propertyChange: PropertyChangeSupport) {
var field: Book = _book
operator fun getValue(shelf3: Shelf3, prop: KProperty<*>): Book = field
operator fun setValue(shelf3: Shelf3, prop: KProperty<*>, newValue: Book) {
val oldBook = field
field = newValue
propertyChange.firePropertyChange("book", oldBook, newValue)
}
}
class Shelf3(val name: String, _book: Book) : BasePropertyChange() {
var book: Book by BookDelegate2(_book, propertyChange)
fun addBookChangeListener(propertyChangeListener: PropertyChangeListener) {
addChangeListener("book", propertyChangeListener)
}
fun removeBookChangeListener(propertyChangeListener: PropertyChangeListener) {
removeChangeListener("book", propertyChangeListener)
}
}
通常借助ReadWriteProperty接口能方便我们实现委托
class BookDelegate3(var field: Book, val propertyChange: PropertyChangeSupport) : ReadWriteProperty {
override fun getValue(thisRef: Shelf3_1, property: KProperty<*>): Book {
return field
}
override fun setValue(thisRef: Shelf3_1, property: KProperty<*>, value: Book) {
val oldBook = field
field = value
propertyChange.firePropertyChange("book", oldBook, value)
}
}
class Shelf3_1(val name: String, _book: Book) : BasePropertyChange() {
var book: Book by BookDelegate3(_book, propertyChange)
fun addBookChangeListener(propertyChangeListener: PropertyChangeListener) {
addChangeListener("book", propertyChangeListener)
}
fun removeBookChangeListener(propertyChangeListener: PropertyChangeListener) {
removeChangeListener("book", propertyChangeListener)
}
}
测试上述代码:
fun testDelegateFieldForKotlin() {
val shelf = Shelf3_1("书架", Book("Think in java"))
shelf.addBookChangeListener(object : PropertyChangeListener {
override fun propertyChange(evt: PropertyChangeEvent?) {
val oldBook = evt?.oldValue as Book
val newBook = evt?.newValue as Book
println("Kotlin委托 old book is $oldBook, and new book is $newBook")
}
})
shelf.book = Book("Kotlin in action!")
}
运行结果如下:
Kotlin委托 old book is Book(name=Think in java), and new book is Book(name=Kotlin in action!)
委托属性的本质:把属性访问器的实现交给了by后的委托对象
其实,Delegate.observable()类实现了上面提到的所有逻辑了。
我们看下Delegate.observable方法的源码
public inline fun observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
该方法返回ObservableProperty对象,看下ObservableProperty对象源码
public abstract class ObservableProperty(initialValue: T) : ReadWriteProperty {
private var value = initialValue
protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true
protected open fun afterChange (property: KProperty<*>, oldValue: T, newValue: T): Unit {}
public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value
}
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val oldValue = this.value
if (!beforeChange(property, oldValue, value)) {
return
}
this.value = value
afterChange(property, oldValue, value)
}
}
该对象有getValue和setValue方法,这和我们自己实现的BookDelegate3类里的getValue和setValue方法逻辑几乎相同。不同之处是,官方还多了beforeChange()控制,和afterChange()供实现类覆盖重写。
Kotlin标准库已经提供了可观察属性的属性委托实现了
class Shelf4(val name: String, _book: Book) {
var book: Book by Delegates.observable(_book, {property, oldValue, newValue ->
println("The old book's name is \"${oldValue.name}\", and the new book's name is \"${newValue.name}\"")
})
}
测试下上述的代码
fun testObserverFieldForKotlin(){
val shelf = Shelf4("书架", Book("think in java"))
shelf.book = Book("Kotlin in action")
}
运行结果如下
The old book’s name is “think in java”, and the new book’s name is “Kotlin in action”
Kotlin也为我们提供了现成的委托类来实现限制属性
class Shelf5(val name: String, val book: Book ,_year: Int) {
var year: Int by Delegates.vetoable(_year, {property, oldValue, newValue ->
newValue <= 99
})
}
测试上述代码
fun testVetoableFieldForKotlin(){
val shelf = Shelf5("书架", Book("think in java"), 0)
shelf.year = 200
println("current book is ${shelf.year}")
shelf.year = 20
println("current book is ${shelf.year}")
}
运行结果如下:
current book is 0
current book is 20
注意:
上述用的是成员函数,事实上,扩展函数也能实现委托属性
MapAccessors.kt文件里,有如下扩展函数源码
@kotlin.jvm.JvmName("getVarContravariant")
@kotlin.internal.LowPriorityInOverloadResolution
@kotlin.internal.InlineOnly
public inline fun MutableMap<in String, in V>.getValue(thisRef: Any?, property: KProperty<*>): V
= @Suppress("UNCHECKED_CAST") (getOrImplicitDefault(property.name) as V)
@kotlin.internal.InlineOnly
public inline operator fun MutableMap<in String, in V>.setValue(thisRef: Any?, property: KProperty<*>, value: V) {
this.put(property.name, value)
}
由此可见, MutableMap存在getValue方法和setValue方法,那么就可以用于委托,事实上,也确实如此。
举个例子:
class Fruit(name: String) : Food(name){
private val attributeMap = HashMap()
val color: String by attributeMap
val size: String by attributeMap
fun setAttributeMap(name: String, value: String){
attributeMap.put(name, value)
}
}
fun testDelegateMap(){
val fruit = Fruit("西瓜")
fruit.setAttributeMap("color", "绿色")
fruit.setAttributeMap("size", "2kg")
println("color = ${fruit.color}, size = ${fruit.size}")
}
运行结果
color = 绿色, size = 2kg
类委托的本质是:把抽象方法的实现交给了by后的委托对象
属性委托的本质是:把属性访问器的实现交给了by后的委托对象
扩展函数也能实现属性委托
维基百科:惰性初始化模式
维基百科:惰性求值