【Kotlin】九、Application單例化和屬性的Delegated

前言

我們很快要去實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫(kù),如果我們想要保持我們代碼的簡(jiǎn)潔性和層次性(而
不是把所有代碼添加到Activity中),我們就要需要有一個(gè)更簡(jiǎn)單的訪問(wèn)application
context的方式眶拉。

Applicaton單例化

按照我們?cè)贘ava中一樣創(chuàng)建一個(gè)單例最簡(jiǎn)單的方式:

class App : Application() {
    companion object {
        private var instance: Application? = null
        fun instance() = instance!!
    }
    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

為了這個(gè)Application實(shí)例被使用,要記得在 AndroidManifest.xml 中增加這
個(gè) App :

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme"
    android:name=".ui.App">
    ...
</application>

Android有一個(gè)問(wèn)題憔儿,就是我們不能去控制很多類(lèi)的構(gòu)造函數(shù)。比如放可,我們不能初
始化一個(gè)非null屬性谒臼,因?yàn)樗闹敌枰跇?gòu)造函數(shù)中去定義。所以我們需要一個(gè)可
null的變量耀里,和一個(gè)返回非null值的函數(shù)蜈缤。我們知道我們一直都有一個(gè) App 實(shí)例,
但是在它調(diào)用 onCreate 之前我們不能去操作任何事情冯挎,所以我們?yōu)榱税踩缘赘纾?br> 們假設(shè) instance() 函數(shù)將會(huì)總是返回一個(gè)非null的 app 實(shí)例。

但是這個(gè)方案看起來(lái)有點(diǎn)不自然房官。我們需要定義個(gè)一個(gè)屬性(已經(jīng)有了getter和
setter)趾徽,然后通過(guò)一個(gè)函數(shù)來(lái)返回那個(gè)屬性。我們有其他方法去達(dá)到相似的效果
么翰守?是的孵奶,我們可以通過(guò)委托這個(gè)屬性的值給另外一個(gè)類(lèi)。這個(gè)就是我們知道的 委托屬性 蜡峰。

委托屬性

我們可能需要一個(gè)屬性具有一些相同的行為了袁,使用 lazy 或者 observable 可以
被很有趣地實(shí)現(xiàn)重用朗恳。而不是一次又一次地去聲明那些相同的代碼,Kotlin提供了
一個(gè)委托屬性到一個(gè)類(lèi)的方法载绿。這就是我們知道的 委托屬性 粥诫。

當(dāng)我們使用屬性的 get 或者 set 的時(shí)候,屬性委托
的 getValue 和 setValue 就會(huì)被調(diào)用崭庸。

屬性委托的結(jié)構(gòu)如下:

class Delegate<T> : ReadWriteProperty<Any?, T> {
    fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return ...
    }
    fun setValue(thisRef: Any?, property: KProperty<*>, value: T){
        ...
    }
}

這個(gè)T是委托屬性的類(lèi)型怀浆。 getValue 函數(shù)接收一個(gè)類(lèi)的引用和一個(gè)屬性的元數(shù)
據(jù)。 setValue 函數(shù)又接收了一個(gè)被設(shè)置的值冀自。如果這個(gè)屬性是不可修改
(val)揉稚,就會(huì)只有一個(gè) getValue 函數(shù)。

下面展示屬性委托是怎么設(shè)置的:

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

它使用了 by 這個(gè)關(guān)鍵字來(lái)指定一個(gè)委托對(duì)象熬粗。

標(biāo)準(zhǔn)委托

在Kotlin的標(biāo)準(zhǔn)庫(kù)中有一系列的標(biāo)準(zhǔn)委托搀玖。它們包括了大部分有用的委托,但是我
們也可以創(chuàng)建我們自己的委托驻呐。

Lazy

它包含一個(gè)lambda灌诅,當(dāng)?shù)谝淮螆?zhí)行 getValue 的時(shí)候這個(gè)lambda會(huì)被調(diào)用,所以
這個(gè)屬性可以被延遲初始化含末。之后的調(diào)用都只會(huì)返回同一個(gè)值猜拾。這是非常有趣的特
性, 當(dāng)我們?cè)谒鼈兊谝淮握嬲{(diào)用之前不是必須需要它們的時(shí)候佣盒。我們可以節(jié)省內(nèi)
存挎袜,在這些屬性真正需要前不進(jìn)行初始化。

class App : Application() {
    val database: SQLiteOpenHelper by lazy {
        MyDatabaseHelper(applicationContext)
    }
    override fun onCreate() {
        super.onCreate()
        val db = database.writableDatabase
    }
}

在這個(gè)例子中肥惭,database并沒(méi)有被真正初始化盯仪,直到第一次調(diào)用 onCreate 時(shí)。
在那之后蜜葱,我們才確保applicationContext存在全景,并且已經(jīng)準(zhǔn)備好可以被使用
了。 lazy 操作符是線程安全的牵囤。

如果你不擔(dān)心多線程問(wèn)題或者想提高更多的性能爸黄,你也可以使
用 lazy(LazyThreadSafeMode.NONE){ ... } 。

Observable

這個(gè)委托會(huì)幫我們監(jiān)測(cè)我們希望觀察的屬性的變化揭鳞。當(dāng)被觀察屬性的 set 方法被
調(diào)用的時(shí)候炕贵,它就會(huì)自動(dòng)執(zhí)行我們指定的lambda表達(dá)式。所以一旦該屬性被賦了新
的值野崇,我們就會(huì)接收到被委托的屬性鲁驶、舊值和新值。

class ViewModel(val db: MyDatabase) {
    var myProperty by Delegates.observable("") {d, old, new ->db.saveChanges(this,new)}
}

這個(gè)例子展示了舞骆,一些我們需要關(guān)心的ViewMode钥弯,每次值被修改了径荔,就會(huì)保存它
們到數(shù)據(jù)庫(kù)。

Vetoable

這是一個(gè)特殊的 observable 脆霎,它讓你決定是否這個(gè)值需要被保存总处。它可以被用
于在真正保存之前進(jìn)行一些條件判斷。

var positiveNumber = Delegates.vetoable(0) {d, old, new ->new >= 0}

上面這個(gè)委托只允許在新的值是正數(shù)的時(shí)候執(zhí)行保存睛蛛。在lambda中鹦马,最后一行表示
返回值。你不需要使用return關(guān)鍵字(實(shí)質(zhì)上不能被編譯)忆肾。

Not Null

有時(shí)候我們需要在某些地方初始化這個(gè)屬性荸频,但是我們不能在構(gòu)造函數(shù)中確定,或
者我們不能在構(gòu)造函數(shù)中做任何事情客冈。第二種情況在Android中很常見(jiàn):在
Activity旭从、fragment、service场仲、receivers……無(wú)論如何和悦,一個(gè)非抽象的屬性在構(gòu)造函
數(shù)執(zhí)行完之前需要被賦值。為了給這些屬性賦值渠缕,我們無(wú)法讓它一直等待到我們希
望給它賦值的時(shí)候鸽素。我們至少有兩種選擇方案。

第一種就是使用可null類(lèi)型并且賦值為null亦鳞,直到我們有了真正想賦的值馍忽。但是我們
就需要在每個(gè)地方不管是否是null都要去檢查。如果我們確定這個(gè)屬性在任何我們
使用的時(shí)候都不會(huì)是null燕差,這可能會(huì)使得我們要編寫(xiě)一些必要的代碼了遭笋。

第二種選擇是使用 notNull 委托。它會(huì)含有一個(gè)可null的變量并會(huì)在我們?cè)O(shè)置這個(gè)
屬性的時(shí)候分配一個(gè)真實(shí)的值谁不。如果這個(gè)值在被獲取之前沒(méi)有被分配,它就會(huì)拋出
一個(gè)異常徽诲。

這個(gè)在單例App這個(gè)例子中很有用:

class App : Application() {
    companion object {
        var instance: App by Delegates.notNull()
    }
    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

從Map中映射值

另外一種屬性委托方式就是刹帕,屬性的值會(huì)從一個(gè)map中獲取value,屬性的名字對(duì)應(yīng)
這個(gè)map中的key谎替。這個(gè)委托可以讓我們做一些很強(qiáng)大的事情偷溺,因?yàn)槲覀兛梢院芎?jiǎn)
單地從一個(gè)動(dòng)態(tài)地map中創(chuàng)建一個(gè)對(duì)象實(shí)例。如果我們import
kotlin.properties.getValue 钱贯,我們可以從構(gòu)造函數(shù)映射到 val 屬性來(lái)得到
一個(gè)不可修改的map挫掏。如果我們想去修改map和屬性,我們也可以import
kotlin.properties.setValue 秩命。類(lèi)需要一個(gè) MutableMap 作為構(gòu)造函數(shù)的參
數(shù)尉共。

想象我們從一個(gè)Json中加載了一個(gè)配置類(lèi)褒傅,然后分配它們的key和value到一個(gè)map
中。我們可以僅僅通過(guò)傳入一個(gè)map的構(gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)實(shí)例:

import kotlin.properties.getValue

class Configuration(map: Map<String, Any?>) {
    val width: Int by map
    val height: Int by map
    val dp: Int by map
    val deviceName: String by map
}

作為一個(gè)參考袄友,這里我展示下對(duì)于這個(gè)類(lèi)怎么去創(chuàng)建一個(gè)必須要的map:

conf = Configuration(mapOf(
    "width" to 1080,
    "height" to 720,
    "dp" to 240,
    "deviceName" to "mydevice"
))

怎么去創(chuàng)建一個(gè)自定義的委托

先來(lái)說(shuō)說(shuō)我們要實(shí)現(xiàn)什么殿托,舉個(gè)例子,我們創(chuàng)建一個(gè) notNull 的委托剧蚣,它只能被
賦值一次支竹,如果第二次賦值,它就會(huì)拋異常鸠按。

Kotlin庫(kù)提供了幾個(gè)接口礼搁,我們自己的委托必須要實(shí)
現(xiàn): ReadOnlyProperty 和 ReadWriteProperty 。具體取決于我們被委托的對(duì)
象是 val 還是 var 目尖。

我們要做的第一件事就是創(chuàng)建一個(gè)類(lèi)然后繼承 ReadWriteProperty :

private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any
?, T> {
override fun getValue(thisRef: Any?, property: KProperty
<*>): T {
throw UnsupportedOperationException()
}
override fun setValue(thisRef: Any?, property: KProperty
<*>, value: T) {
}
}

這個(gè)委托可以作用在任何非null的類(lèi)型馒吴。它接收任何類(lèi)型的引用,然后像getter和
setter那樣使用T”把悖現(xiàn)在我們需要去實(shí)現(xiàn)這些函數(shù)募书。

  • Getter函數(shù) 如果已經(jīng)被初始化,則會(huì)返回一個(gè)值测蹲,否則會(huì)拋異常莹捡。
  • Setter函數(shù) 如果仍然是null,則賦值扣甲,否則會(huì)拋異常篮赢。
private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any
?, T> {
private var value: T? = null
override fun getValue(thisRef: Any?, property: KProperty<*>)
: T {
return value ?: throw IllegalStateException("${desc.name
} " +
"not initialized")
}
override fun setValue(thisRef: Any?, property: KProperty<*>,
value: T) {
this.value = if (this.value == null) value
else throw IllegalStateException("${desc.name} already i
nitialized")
}
}

現(xiàn)在你可以創(chuàng)建一個(gè)對(duì)象,然后添加函數(shù)使用你的委托:

object DelegatesExt {
    fun notNullSingleValue<T>():
        ReadWriteProperty<Any?, T> = NotNullSingleValueVar()
}

重新實(shí)現(xiàn)Application單例化

在這個(gè)情景下琉挖,委托就可以幫助我們了启泣。我們直到我們的單例不會(huì)是null,但是我
們不能使用構(gòu)造函數(shù)去初始化屬性示辈。所以我們可以使用 notNull 委托:

class App : Application() {
    companion object {
        var instance: App by Delegates.notNull()
    }
    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

這種情況下有個(gè)問(wèn)題寥茫,我們可以在app的任何地方去修改這個(gè)值,因?yàn)槿绻覀兪?br> 用 Delegates.notNull() 矾麻,屬性必須是var的纱耻。但是我們可以使用剛剛創(chuàng)建的委
托,這樣可以多一點(diǎn)保護(hù)险耀。我們只能修改這個(gè)值一次:

companion object {
    var instance: App by DelegatesExt.notNullSingleValue()
}

盡管弄喘,在這個(gè)例子中,使用單例可能是最簡(jiǎn)單的方法甩牺,但是我想用代碼的形式展示
給你怎么去創(chuàng)建一個(gè)自定義的委托蘑志。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子急但,更是在濱河造成了極大的恐慌澎媒,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羊始,死亡現(xiàn)場(chǎng)離奇詭異旱幼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)突委,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)柏卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人匀油,你說(shuō)我怎么就攤上這事缘缚。” “怎么了敌蚜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵桥滨,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我弛车,道長(zhǎng)齐媒,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任纷跛,我火速辦了婚禮喻括,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贫奠。我一直安慰自己唬血,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布唤崭。 她就那樣靜靜地躺著拷恨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谢肾。 梳的紋絲不亂的頭發(fā)上腕侄,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音芦疏,去河邊找鬼冕杠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛眯分,可吹牛的內(nèi)容都是我干的拌汇。 我是一名探鬼主播柒桑,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼弊决,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起飘诗,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤与倡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后昆稿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體纺座,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年溉潭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了净响。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喳瓣,死狀恐怖馋贤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情畏陕,我是刑警寧澤配乓,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站惠毁,受9級(jí)特大地震影響犹芹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鞠绰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一腰埂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洞豁,春花似錦盐固、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至曙咽,卻和暖如春蛔趴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背例朱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工孝情, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人洒嗤。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓箫荡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親渔隶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子羔挡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • 前言 人生苦多洁奈,快來(lái) Kotlin ,快速學(xué)習(xí)Kotlin绞灼! 什么是Kotlin利术? Kotlin 是種靜態(tài)類(lèi)型編程...
    任半生囂狂閱讀 26,218評(píng)論 9 118
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)低矮,斷路器印叁,智...
    卡卡羅2017閱讀 134,708評(píng)論 18 139
  • 函數(shù)和對(duì)象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對(duì)于任何一門(mén)語(yǔ)言來(lái)說(shuō)都是核心的概念军掂。通過(guò)函數(shù)可以封裝任意多條語(yǔ)句轮蜕,而且...
    道無(wú)虛閱讀 4,581評(píng)論 0 5
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類(lèi)相關(guān)的語(yǔ)法蝗锥,內(nèi)部類(lèi)的語(yǔ)法肠虽,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法玛追,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,664評(píng)論 18 399
  • java分布式應(yīng)用如何入門(mén)
    塵世的魚(yú)閱讀 228評(píng)論 0 2