一榄鉴、概述
Kotlin有很多語(yǔ)法糖押袍,最近看了委托屬性, 用于筆者的開(kāi)源組件LightKV, 確實(shí)提高了不少易用性沦童。
關(guān)于LightKV,筆者在上一篇文章《LightKV-高性能key-value存儲(chǔ)組件》中有介紹LightKV的原理柜候,有興趣的讀者可以了解一下搞动。
LightKV的用法和SharePreferences類(lèi)似,都是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ǔ)類(lèi)都需要實(shí)現(xiàn)一份各種類(lèi)型的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)像是在直接訪(fǎng)問(wèn)AppData的屬性。
四妨马、屬性委托的實(shí)現(xiàn)
4.1 聲明屬性
語(yǔ)法: val/var <屬性名>: <類(lèi)型> 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 的話(huà))梧喷,并以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 訪(fǎng)問(wèn)屬性
訪(fǎng)問(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 定義抽象類(lèi)
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()
}
}
該抽象類(lèi)聲明了LightKV, 添加了自動(dòng)提交開(kāi)關(guān)匾嘱,以及定了個(gè)各種類(lèi)型委托斤斧。
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è)類(lèi)型委托撬讽,我們?cè)跇?gòu)造函數(shù)傳入key, 由key決定對(duì)應(yīng)的類(lèi)型操作。
通過(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昧捷,聲明屬性闲昭,即可像訪(fǎng)問(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ò)的。