一、概述
Kotlin有很多語法糖,最近看了委託屬性, 用於改造筆者的開源組件LightKV, 確實提高了不少易用性。
關於LightKV,筆者在上一篇文章《LightKV-高性能key-value存儲組件》中有介紹其原理,有興趣的讀者可以瞭解一下。
LightKV的用法和SharePreferences類似,都是key-value結構,通過指定key讀寫value。
key-value 的 API 適用於存儲統計,緩存,配置......等各種信息,
隨着APP的迭代,必然會有越來越多的信息需要存儲,對應用開發而言,key-value的存儲不可或缺。
筆者上一篇文章中,有熱心網友提到:“想法很好,不過感覺用處不大,如果要存的數據很少那就sp …… ”
誠然,SDK已經提供了SharePreferences了,而且當用SharePreferences還沒遇到性能瓶頸時,也就沒有嘗試別的組件的的動力了。
而且,之前的那一版,只做到了“高效”,沒有做到“易用”。
二、舊版用法
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, "[email protected]")
}
該用法的複雜度在於:
如果想用靜態方法(調用時簡單一些),則每一個數據存儲類都需要實現一份各種類型的get和set;
如果直接返回data()來讀寫, 寫起來會比較長:
val account = AppData2.data().getString(Keys.ACCOUNT)
if(TextUtils.isEmpty(account)){
AppData2.data().putString(Keys.ACCOUNT, "[email protected]")
}
直到後來瞭解了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 = "[email protected]"
}
使用Kotlin委託,省了各種put和set的方法調用,看起來像是在直接訪問AppData的屬性。
四、屬性委託的實現
4.1 聲明屬性
語法: val/var <屬性名>: <類型> by <表達式>。
class Example {
var p: String by Delegate()
}
by 後面的表達式是對應的委託, 屬性的 get() 和 set() 會被委託給它的 getValue() 和 setValue() 方法。
當然,如果聲明的是val, 則不會委託set()方法。
4.2 實現委託
屬性的委託,需要提供一個 getValue() 函數和 setValue() 函數(如果聲明的是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 的引用, 參數 property 保存了對屬性p的描述,例如可以通過property.name獲取p的名字.
4.3 訪問屬性
訪問 p 時,將調用 Delegate 中的 getValue() 函數;
給 p 賦值時,將調用 setValue() 函數。
val e = Example()
println(e.p)
e.p = "NEW"
輸出結果:
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()
}
// 由編譯器生成的相應代碼:
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)
}
前後對比,不難看出,其實屬性委託的本質是“代理模式”的語法封裝。
五、優化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, 添加了自動提交開關,以及定了個各種類型委託。
5.2 實現委託
爲方便編寫委託, Kotlin標準庫定義了的ReadWriteProperty接口:
interface ReadWriteProperty<in R, T> {
operator fun getValue(thisRef: R, property: KProperty<*>): T
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
使用時實現接口的方法即可。
爲了統一定義各個類型委託,我們在構造函數傳入key, 由key決定對應的類型操作。
通過thisRef.data(LightKV)和 key, 分別在getValue和setValue方法中實現取值和賦值。
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時自動commit()。
當然,如果需要批量提交。可以調用disableAutoCommit()禁用自動提交。
最後,在使用時,繼承KVData,聲明屬性,即可像訪問變量一樣讀寫LightKV的數據(參見第三節)。
六、下載
repositories {
jcenter()
}
dependencies {
implementation 'com.horizon.lightkv:lightkv:1.0.4'
}
項目地址:
https://github.com/No89757/LightKV
七、結語
以前筆者對語法糖是不感興趣的,覺得語法糖掩蓋了細節,容易使人“只知其然而不知其所以然”;
但是後來漸漸地也開始接受了,技術的發展日新月異,不可能什麼都從底層開始構築。
業界流傳有“人生苦短,我用python”,說的就是高級語言所帶來的便利,可以節約不少時間。
當然,C語言,彙編語言,還是需要有人去寫,要看問題領域。
對APP開發而言,誠然有大量的“搬磚”工作,磚頭搬累了,來一發語法糖,也是不錯的。