Kotlin-Android世界的一股清流-委託

源碼地址:https://github.com/cn-ljb/KotlinBlogs

委託

一、委託類

什麼是委託類?

代理設計模式,在Java中實現一個簡單的代理模式如下:

//抽象功能
public interface Base {

    void doSome();

}

//實際操作類
public class BaseImpl implements Base {

    @Override
    public void doSome() {
        System.out.println("java do some thing.");
    }
}

//代理類
public class BaseProxy implements Base {

    private Base impl;

    public BaseProxy(Base impl) {
        this.impl = impl;
    }

    @Override
    public void doSome() {
        impl.doSome();
    }
}

Kotlin在語法上提供by關鍵字,所以實現以上代碼,可以更簡潔點:

interface Base {
    fun doSome()
}


class BaseImpl : Base {

    override fun doSome() {
        println("do some thing...")
    }

}

class BaseProxy(val b: Base) : Base by b

當然,你可以在代理中擴展doSome函數,只需覆寫它即可

class BaseProxy(val b: Base) : Base by b{

    override fun doSome() {
        println("pre")
        b.doSome()
        println("next")
    }
}

二、委託屬性

委託類並不是重點,主要看看委託屬性。

什麼是委託屬性?

就如同代理設計模式一樣 在調用屬性或者賦值屬性值時,我們希望做些額外操作,Kotlin基本語法如下:

val/var <property name>: <Type> by <expression>

by 後面的表達式就是代理,因爲get() set() 對應的屬性會被 getValue() setValue() 方法代理。屬性代理不需要任何接口的實現,但必須要提供 getValues() 函數(如果是 var 還需要 setValue())。

例如:

class Person() {

    var name: String by PrefixName()

    constructor(name: String) : this() {
        this.name = name
    }

}


class PrefixName {

    private var proxyName: String = "admin_"

    operator fun getValue(person: Person, property: KProperty<*>): String {
        return "admin_$proxyName"
    }

    operator fun setValue(person: Person, property: KProperty<*>, s: String) {
        proxyName = s
    }

}

我們將Person的name屬性委託給了PrefixName類,該類提供了該屬性新的getValue()和setValue()函數,將來在使用Person的name屬性時,實際訪問的是該類中的代理。

//代理屬性
val per = Person("Jake")
println(per.name)
per.name = "haha"
println(per.name)

輸出:

admin_Jake
admin_haha

很多同學可能會納悶,輸出結果中可以看出一直使用的是PrefixName中的返回的屬性,那Person中定義的name成員有什麼意義,不能直接操作嗎?

其實,Person中是沒有name屬性的,定義語句只是Kotlin中的語法格式罷了。不信?我們反編譯字節碼,看看Java代碼中到底有些什麼東西。

反編譯爲Java代碼:

雖然Java代碼裏存在setter和getter方法,但是並沒有name屬性。也就是說我們的猜想是沒有錯的,Person中的name屬性被代理後,實際Person類中並不存在該屬性,從而替代它的是代理對象

無論是Person中的setter還是getter,實際調用的都是代理對象中setValue,getValue ,不妨看下反編譯後的代理屬性類是什麼樣子的:

發現除了我們自己寫的代碼外,只多了幾行檢查參數的代碼,也就是說Kotlin強制要求代理屬性裏的參setValue()\getValue()參數數必須不爲null,如果參數爲null,則拋出異常。

注意:被定義爲代理的屬性,該類中實際是不存在的

理解這句話,不要被Kotlin的語法所迷惑。

標準委託屬性

我們除了可以自定義委託屬性外,Kotlin也爲我們提供了集中常用的委託屬性。

1、延遲屬性

lazy()

該委託可以保證屬性在使用是才被初始化出來

例如:

val str: String by lazy { "abc" }

定義時,在lazy後面的Lambda表達式進行初始化操作即可。細心的朋友也已經注意到lazy其實就是一個接收函數的函數(可以直接傳遞Lambda表達式)。

貌似有點不對啊,委託屬性by關鍵字之後寫的對象類,不是應該提供setValue()\getValue()函數嗎?
那麼我們不妨繼續看下lazy()函數,發現其返回的是Lazy的一個對象。

然而Lazy是個接口,那麼實際SynchronizedLazyImpl應該是Lazy中的子類對象,那麼這個子類對象有setValue()\getValue()函數嗎

我們發現子類對象,也沒有這兩個函數,這不科學啊,不滿足定義一個委託屬性的規則啊。先別急,別忘了,Kotlin中有個神奇的特性:擴展函數。

原來Lazy的getValue()是通過擴展函數實現的,那麼setValue()呢?很遺憾,並沒有,也就是說lazy委託只能給只讀屬性(val)使用:使用時初始化,初始化之後不能再進行賦值(當然你可以手動給Lazy添加一個擴展setValue()函數,但是如果這樣便曲解了lazy委託的意義)。

既然這樣,我們不妨在看看lazy是怎麼實現的,其實讓我們自己實現也完全沒問題吧。

繼續看實際的Lazy實現類 SynchronizedLazyImpl類, 注意裏面的value屬性的getter()函數,擴展的getValue()函數返回的就是這個屬性。

使用起來也跟普通的屬性沒有任何區別

//...沒使用前是不會被初始化的
println(str)  //輸出:abc

如果該屬性需要多線程訪問,爲了保證線程安全,lazy()函數提供了兩個參數的重載函數:

//多線程安全
val str2: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "bcd" }

2、可觀察屬性

Delegates.observable()

有點類似於觀察者設計模式,當屬性值發生改變時,發出事件並做出響應事件。

怎麼用呢?

class Person() {

    var name: String by PrefixName()

    var age: Int by Delegates.observable(0, {
        property, oldValue, newValue ->
        println("屬性名:${property.name} , 舊值:$oldValue ,  新值:$newValue")
    })

    constructor(name: String) : this() {
        this.name = name
    }

}

我們爲Person類添加一個age屬性,該屬性使用了可觀察委託,第一個參數是屬性初始值,一但屬性值發生改變,第二個參數裏的Lambda(函數)就會被調用。

調用:

per.age = 10

輸出:

屬性名:age , 舊值:0 ,  新值:10

那麼底層的實現代碼過程就不帶大家看了,套路是一樣,by之後的對象類提供setValue()\getValue()函數,貼出主要部分,具體的實現感興趣的同學按照這個套路看源碼即可,不難~

3、Map存儲屬性

map

該委託用於將map中存儲的屬性讀取到Mode中,Kotlin官方考慮到如果存在一個Map集合,並且該集合的鍵值對對應Mode中的屬性,那麼可以使用該委託(實用性有待考察)。

這個就不講了,個人感覺有點雞肋,Json解析有各種解析工具(Gson、fastjson等),可能是個人使用場景使用不到吧,上一個官方demo結束。

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

在這個例子中,構造函數接受一個 map :

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

var 屬性可以用 MutableMap 代替只讀的 Map:

class MutableUser(val map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int     by map
}

總結

除了瞭解常用的委託外,最主要的還是理解一個委託它究竟做了什麼,怎麼實現的?哪裏不明白,反編譯看Java源碼可能會更直接,不得不承認Kotlin的語法糖太多,有些地方真的閱讀起來比較晦澀,不過沒事,誰讓咱們懂Java。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章