Kotlin委托屬性-簡(jiǎn)化數(shù)據(jù)訪問(wèn)

一、概述

Kotlin有很多語(yǔ)法糖,最近看了委托屬性, 用于筆者的開(kāi)源組件LightKV, 確實(shí)提高了不少易用性。
關(guān)于LightKV,筆者在上一篇文章《LightKV-高性能key-value存儲(chǔ)組件》中有介紹LightKV的原理,有興趣的讀者可以了解一下。

LightKV的用法和SharePreferences類似,都是key-value結(jié)構(gòu),通過(guò)指定key讀寫(xiě)value。
key-value 的 API 適用于存儲(chǔ)統(tǒng)計(jì),緩存,配置......等各種信息,
隨著APP的迭代,必然會(huì)有越來(lái)越多的信息需要存儲(chǔ),對(duì)應(yīng)用開(kāi)發(fā)而言,key-value的存儲(chǔ)不可或缺。

最初發(fā)布LightKV的時(shí)候,有熱心網(wǎng)友提到:“想法很好,不過(guò)感覺(jué)用處不大,如果要存的數(shù)據(jù)很少那就sp …… ”
誠(chéng)然,SDK已經(jīng)提供了SharePreferences了,而且當(dāng)用SharePreferences還沒(méi)遇到性能瓶頸時(shí),也就沒(méi)有嘗試別的組件的的動(dòng)力了。

而且,之前的那一版(在引入委托屬性之前),只做到了“高效”,沒(méi)有做到“易用”。


二、舊版用法

public class AppData {
    private static final SyncKV DATA =
            new LightKV.Builder(GlobalConfig.getAppContext(), "app_data")
                    .logger(AppLogger.getInstance())
                    .executor(AsyncTask.THREAD_POOL_EXECUTOR)
                    .keys(Keys.class)
                    .encoder(new ConfuseEncoder())
                    .sync();

    // keys define
    public interface Keys {
        int SHOW_COUNT = 1 | DataType.INT;
        int ACCOUNT = 2 | DataType.STRING ;
        int TOKEN = 3 | DataType.STRING;
        int SECRET = 4 | DataType.ARRAY | DataType.ENCODE;
    }

    public static SyncKV data() {
        return DATA;
    }

    public static String getString(int key) {
        return DATA.getString(key);
    }

    public static void putString(int key, String value) {
        DATA.putString(key, value);
        DATA.commit();
    }

    public static byte[] getArray(int key) {
        return DATA.getArray(key);
    }

    public static void putArray(int key, byte[] value) {
        DATA.putArray(key, value);
        DATA.commit();
    }

    // ......
}
val account = AppData.getString(AppData.Keys.ACCOUNT)
if(TextUtils.isEmpty(account)){
      AppData.putString(AppData.Keys.ACCOUNT, "foo@gmail.com")
}

該用法的復(fù)雜度在于:
如果想用靜態(tài)方法(調(diào)用時(shí)簡(jiǎn)單一些),則每一個(gè)數(shù)據(jù)存儲(chǔ)類都需要實(shí)現(xiàn)一份各種類型的get和set;
如果直接返回data()來(lái)讀寫(xiě), 寫(xiě)起來(lái)會(huì)比較長(zhǎng):

val account = AppData2.data().getString(Keys.ACCOUNT)
if(TextUtils.isEmpty(account)){
     AppData2.data().putString(Keys.ACCOUNT, "foo@gmail.com")
}

直到后來(lái)了解了Kotlin委托, 仿佛看到了曙光……


三、新版用法

object AppData : KVData() {
    override fun createInstance(): LightKV {
        return LightKV.Builder(GlobalConfig.appContext, "app_data")
                .logger(AppLogger)
                .executor(AsyncTask.THREAD_POOL_EXECUTOR)
                .encoder(GzipEncoder)
                .sync()
    }

    var showCount by int(1)
    var account by string(2)
    var token by string(3)
    var secret by array(4 or DataType.ENCODE)
}
val account = AppData.account
if (TextUtils.isEmpty(account)) {
   AppData.account = "foo@gmail.com"
}

使用Kotlin委托,省了各種put和set的方法調(diào)用,看起來(lái)像是在直接訪問(wèn)AppData的屬性。


四、屬性委托的實(shí)現(xiàn)

4.1 聲明屬性

語(yǔ)法: val/var <屬性名>: <類型> by <表達(dá)式>。

class Example {
    var p: String by Delegate()
}

by 后面的表達(dá)式是對(duì)應(yīng)的委托, 屬性的 get() 和 set() 會(huì)被委托給它的 getValue() 和 setValue() 方法。
當(dāng)然,如果聲明的是val, 則不會(huì)委托set()方法。

4.2 實(shí)現(xiàn)委托

屬性的委托,需要提供一個(gè) getValue() 函數(shù)和 setValue() 函數(shù)(如果聲明的是var 的話),并以operator修飾。

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

例子中,thisRef 是 Example 的引用, 參數(shù) property 保存了對(duì)屬性p的描述,例如可以通過(guò)property.name獲取p的名字.

4.3 訪問(wèn)屬性

訪問(wèn) p 時(shí),將調(diào)用 Delegate 中的 getValue() 函數(shù);
給 p 賦值時(shí),將調(diào)用 setValue() 函數(shù)。

val e = Example()
println(e.p)
e.p = "NEW"

輸出結(jié)果:

Example@33a17727, thank you for delegating ‘p’ to me!
NEW has been assigned to ‘p’ in Example@33a17727.

4.4 屬性委托的原理

class C {
    var prop: Type by MyDelegate()
}
// 由編譯器生成的相應(yīng)代碼:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

前后對(duì)比,不難看出,其實(shí)屬性委托的本質(zhì)是“代理模式”的語(yǔ)法封裝。

五、優(yōu)化LightKV

5.1 定義抽象類

abstract class KVData{
    internal var autoCommit = true

    abstract fun createInstance() : LightKV

    val data: LightKV by lazy {
        createInstance()
    }

    protected fun boolean(key: Int) = KVProperty<Boolean>(key or DataType.BOOLEAN)
    protected fun int(key: Int) = KVProperty<Int>(key or DataType.INT)
    protected fun float(key: Int) = KVProperty<Float>(key or DataType.FLOAT)
    protected fun double(key: Int) = KVProperty<Double>(key or DataType.DOUBLE)
    protected fun long(key: Int) = KVProperty<Long>(key or DataType.LONG)
    protected fun string(key: Int) = KVProperty<String>(key or DataType.STRING)
    protected fun array(key: Int) = KVProperty<ByteArray>(key or DataType.ARRAY)

    fun disableAutoCommit(){
        autoCommit = false
    }

    fun enableAutoCommit(){
        autoCommit = true
        data.commit()
    }
}

該抽象類聲明了LightKV, 添加了自動(dòng)提交開(kāi)關(guān),以及定了個(gè)各種類型委托。

5.2 實(shí)現(xiàn)委托

為方便編寫(xiě)委托, Kotlin標(biāo)準(zhǔn)庫(kù)定義了的ReadWriteProperty接口:

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

使用時(shí)實(shí)現(xiàn)接口的方法即可。
為了統(tǒng)一定義各個(gè)類型委托,我們?cè)跇?gòu)造函數(shù)傳入key, 由key決定對(duì)應(yīng)的類型操作。
通過(guò)thisRef.data(LightKV)和 key, 分別在getValue和setValue方法中實(shí)現(xiàn)取值和賦值。

class KVProperty<T>(private val key: Int) : ReadWriteProperty<KVData, T> {
    @Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY")
    override operator fun getValue(thisRef: KVData, property: KProperty<*>): T
    = with(thisRef.data) {
        return when (key and DataType.MASK) {
            DataType.BOOLEAN -> getBoolean(key)
            DataType.INT -> getInt(key)
            DataType.FLOAT -> getFloat(key)
            DataType.LONG -> getLong(key)
            DataType.DOUBLE -> getDouble(key)
            DataType.STRING -> getString(key)
            DataType.ARRAY -> getArray(key)
            else -> throw IllegalArgumentException("Invalid Key: $key")
        } as T
    }

    override operator fun setValue(thisRef: KVData, property: KProperty<*>, value: T)  
    = with(thisRef.data) {
        when (key and DataType.MASK) {
            DataType.BOOLEAN -> putBoolean(key, value as Boolean)
            DataType.INT -> putInt(key, value as Int)
            DataType.FLOAT -> putFloat(key, value as Float)
            DataType.LONG -> putLong(key, value as Long)
            DataType.DOUBLE -> putDouble(key, value as Double)
            DataType.STRING -> putString(key, value as String)
            DataType.ARRAY -> putArray(key, value as ByteArray)
            else -> throw IllegalArgumentException("Invalid Key: $key")
        }
        if(mMode == LightKV.SYNC_MODE && thisRef.autoCommit){
            commit()
        }
    }
}

在LightKV為SYNC_MODE時(shí)自動(dòng)commit()。
當(dāng)然,如果需要批量提交??梢哉{(diào)用disableAutoCommit()禁用自動(dòng)提交。

最后,在使用時(shí),繼承KVData,聲明屬性,即可像訪問(wèn)變量一樣讀寫(xiě)LightKV的數(shù)據(jù)(參見(jiàn)第三節(jié))。


六、下載

repositories {
    jcenter()
}

dependencies {
    implementation 'com.horizon.lightkv:lightkv:1.0.6'
}

項(xiàng)目地址:
https://github.com/No89757/LightKV

七、結(jié)語(yǔ)

以前筆者對(duì)語(yǔ)法糖是不感興趣的,覺(jué)得語(yǔ)法糖掩蓋了細(xì)節(jié),容易使人“只知其然而不知其所以然”;
但是后來(lái)漸漸地也開(kāi)始接受了,技術(shù)的發(fā)展日新月異,不可能什么都從底層開(kāi)始構(gòu)筑。
業(yè)界流傳有“人生苦短,我用python”,說(shuō)的就是高級(jí)語(yǔ)言所帶來(lái)的便利,可以節(jié)約不少時(shí)間。
當(dāng)然,C語(yǔ)言,匯編語(yǔ)言,還是需要有人去寫(xiě),要看問(wèn)題領(lǐng)域。
對(duì)APP開(kāi)發(fā)而言,誠(chéng)然有大量的“搬磚”工作,磚頭搬累了,來(lái)一發(fā)語(yǔ)法糖,也是不錯(cuò)的。


最后編輯于
?著作權(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)容

  • 前言 人生苦多,快來(lái) Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,701評(píng)論 9 118
  • 本文是在學(xué)習(xí)和使用kotlin時(shí)的一些總結(jié)與體會(huì),一些代碼示例來(lái)自于網(wǎng)絡(luò)或Kotlin官方文檔,持續(xù)更新... 對(duì)...
    竹塵居士閱讀 3,485評(píng)論 0 8
  • 寫(xiě)在開(kāi)頭:本人打算開(kāi)始寫(xiě)一個(gè)Kotlin系列的教程,一是使自己記憶和理解的更加深刻,二是可以分享給同樣想學(xué)習(xí)Kot...
    胡奚冰閱讀 958評(píng)論 1 1
  • 我們不再是同一節(jié)奏 你拒絕相見(jiàn)的請(qǐng)求 說(shuō)要學(xué)習(xí)工作研究 誰(shuí)知道是不是借口 我黯然神傷著轉(zhuǎn)頭 更加荒謬的是 我的時(shí)間...
    沈安樂(lè)閱讀 385評(píng)論 0 1
  • 我現(xiàn)在是一名超市收銀員,今天上晚班,我正在服務(wù)臺(tái)里面和同事說(shuō)話。 正聊得開(kāi)心,我一轉(zhuǎn)頭看到一對(duì)來(lái)購(gòu)物的老夫妻在深情...
    孫丹丹86400閱讀 369評(píng)論 5 2

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