【基礎(chǔ)篇】Kotlin第六講-委托類(lèi)和屬性

委托類(lèi)

實(shí)現(xiàn)一個(gè)接口,可以使用by關(guān)鍵字將接口實(shí)現(xiàn)委托給另一個(gè)對(duì)象。

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")
    }
}

類(lèi)委托后我們依然可以通過(guò)重寫(xiě)的方式來(lái)覆蓋委托類(lèi)的實(shí)現(xiàn),這里View實(shí)現(xiàn)onLongClick方法,覆蓋重寫(xiě)了ViewClickDelegate類(lèi)里的onLongClick方法。

類(lèi)委托的本質(zhì)是:把抽象方法的實(shí)現(xiàn)交給了by后的委托對(duì)象

延遲初始化和委托屬性

延遲初始化屬性

不在對(duì)象創(chuàng)建的時(shí)候初始化,而是在第一次使用時(shí)初始化。完成后像普通屬性一樣使用

open class Food(val name: String) {

    override fun toString(): String {
        return "[$name]"
    }
}

class Container(val name: String) {

    lateinit var foodList: List<Food>

}

惰性初始化屬性

第一次使用該屬性時(shí)才初始化,且只初始化一次。用旗號(hào)標(biāo)示是否初始化過(guò),旗號(hào)有多種選擇和實(shí)現(xiàn)方式。

在代碼定義處執(zhí)行初始化,有助于代碼維護(hù)。

對(duì)指令式語(yǔ)言,這個(gè)模式可能潛藏著危險(xiǎn),尤其是使用共享狀態(tài)的程式習(xí)慣。

普通實(shí)現(xiàn)

class Container2(val name: String) {
    private var _foodList: List<Food>? = null
    val foodList: List<Food>
        get() {
            if (_foodList == null) {
                _foodList = arrayListOf(Food("米糊"))
            }
            return _foodList!!
        }
}

by lazy(){}實(shí)現(xiàn)惰性初始化

class Container4(val name: String) {
    val food: Food by lazy{
        Food("米糊")
    }
}

//指定鎖
class Container5(val name: String) {
    val food: Food by lazy(Container5::class){
        Food("米糊")
    }
}

//默認(rèn) 線程安全 SYNCHRONIZED
//PUBLICATION,同步鎖不是必需的,允許多個(gè)線程同時(shí)執(zhí)行
class Container6(val name: String) {
    val food: Food by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
        Food("米糊")
    }
}

在JavaBean的設(shè)計(jì)中,按照屬性的不同作用又細(xì)分為四類(lèi):?jiǎn)沃祵傩裕饕龑傩?;關(guān)聯(lián)屬性,限制屬性。接下來(lái)看下Kotlin如何實(shí)現(xiàn)關(guān)聯(lián)屬性和限制屬性的

關(guān)聯(lián)屬性(可觀察屬性)

通過(guò)PropertyChangeSupport代碼實(shí)現(xiàn)屬性監(jiān)聽(tīng)

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)
    }
}

把邏輯封裝,抽取出基類(lèi)

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訪問(wèn)器的邏輯封裝成一個(gè)類(lèi)

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手工實(shí)現(xiàn)了可觀察屬性變化的功能,測(cè)試下

fun testObserverField() {
    val shelf = Shelf2("書(shū)架", 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")
}

運(yùn)行上述代碼結(jié)果如下:

old book = Book(name=Think in java) , new book = Book(name=Kotlin in action)

使用Kotlin委托實(shí)現(xiàn)

Kotlin的委托屬性在語(yǔ)言層面提供了在屬性的讀訪問(wèn)器里調(diào)用委托類(lèi)里operator修飾的兩參數(shù)getValue方法,屬性寫(xiě)訪問(wèn)器調(diào)用operator修飾setValue三個(gè)參數(shù)方法

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接口能方便我們實(shí)現(xiàn)委托

class BookDelegate3(var field: Book, val propertyChange: PropertyChangeSupport) : ReadWriteProperty<Shelf3_1, Book> {

    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)
    }
}

測(cè)試上述代碼:

fun testDelegateFieldForKotlin() {
    val shelf = Shelf3_1("書(shū)架", 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!")
}

運(yùn)行結(jié)果如下:

Kotlin委托 old book is Book(name=Think in java), and new book is Book(name=Kotlin in action!)

委托屬性的本質(zhì):把屬性訪問(wèn)器的實(shí)現(xiàn)交給了by后的委托對(duì)象

使用Kotlin的自帶的實(shí)現(xiàn)可觀察屬性

其實(shí),Delegate.observable()類(lèi)實(shí)現(xiàn)了上面提到的所有邏輯了。

我們看下Delegate.observable方法的源碼

public inline fun <T> 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對(duì)象,看下ObservableProperty對(duì)象源碼

public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
    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)
    }
}

該對(duì)象有g(shù)etValue和setValue方法,這和我們自己實(shí)現(xiàn)的BookDelegate3類(lèi)里的getValue和setValue方法邏輯幾乎相同。不同之處是,官方還多了beforeChange()控制,和afterChange()供實(shí)現(xiàn)類(lèi)覆蓋重寫(xiě)。

Kotlin標(biāo)準(zhǔn)庫(kù)已經(jīng)提供了可觀察屬性的屬性委托實(shí)現(xiàn)了

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}\"")
    })
}

測(cè)試下上述的代碼

fun testObserverFieldForKotlin(){
    val shelf = Shelf4("書(shū)架", Book("think in java"))
    shelf.book = Book("Kotlin in action")
}

運(yùn)行結(jié)果如下

The old book's name is "think in java", and the new book's name is "Kotlin in action"

限制屬性

Kotlin也為我們提供了現(xiàn)成的委托類(lèi)來(lái)實(shí)現(xiàn)限制屬性

class Shelf5(val name: String, val book: Book ,_year: Int) {
    var year: Int by Delegates.vetoable(_year, {property, oldValue, newValue ->
        newValue <= 99
    })
}

測(cè)試上述代碼

fun testVetoableFieldForKotlin(){
    val shelf = Shelf5("書(shū)架", Book("think in java"), 0)
    shelf.year = 200
    println("current book is ${shelf.year}")

    shelf.year = 20
    println("current book is ${shelf.year}")
}

運(yùn)行結(jié)果如下:

current book is 0
current book is 20

注意:
上述用的是成員函數(shù),事實(shí)上,擴(kuò)展函數(shù)也能實(shí)現(xiàn)委托屬性

使用Map實(shí)現(xiàn)委托屬性

MapAccessors.kt文件里,有如下擴(kuò)展函數(shù)源碼

@kotlin.jvm.JvmName("getVarContravariant")
@kotlin.internal.LowPriorityInOverloadResolution
@kotlin.internal.InlineOnly
public inline fun <V> 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 <V> MutableMap<in String, in V>.setValue(thisRef: Any?, property: KProperty<*>, value: V) {
    this.put(property.name, value)
}

由此可見(jiàn), MutableMap存在getValue方法和setValue方法,那么就可以用于委托,事實(shí)上,也確實(shí)如此。

舉個(gè)例子:

class Fruit(name: String) : Food(name){

    private val attributeMap = HashMap<String, String>()

    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}")
}

運(yùn)行結(jié)果

color = 綠色, size = 2kg

小結(jié)

類(lèi)委托的本質(zhì)是:把抽象方法的實(shí)現(xiàn)交給了by后的委托對(duì)象

屬性委托的本質(zhì)是:把屬性訪問(wèn)器的實(shí)現(xiàn)交給了by后的委托對(duì)象

擴(kuò)展函數(shù)也能實(shí)現(xiàn)屬性委托

參考資料

維基百科:惰性初始化模式

維基百科:惰性求值

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容