在 kotlin 開發(fā)中,會(huì)遇到懶加載的情形:使用 by lazy
關(guān)鍵字。而這是通過(guò)委托來(lái)實(shí)現(xiàn)的。Kotlin 通過(guò)關(guān)鍵字 by 實(shí)現(xiàn)委托妇多。
委托分為類委托和屬性委托伤哺。
一燕侠、類委托
通過(guò)類委托,一個(gè)對(duì)象可以將繼承自某個(gè)接口的函數(shù)全部交由另一個(gè)對(duì)象來(lái)實(shí)現(xiàn)立莉。其基本形式為:
interface Interface {
fun function1(params: String)
fun function2()
}
class Impl(delegate: Interface) : Interface by delegate
class Delegate : Interface {
override fun function1(params: String) {
println("delegate function1 $params")
}
override fun function2() {
println("delegate function2")
}
}
在以上代碼中绢彤,Impl
類的所有繼承自 Interface
接口的函數(shù)都交由 delegate
對(duì)象來(lái)實(shí)現(xiàn)。
類委托的例子
類委托不僅僅只局限于單一接口蜓耻。例如茫舶,假設(shè)現(xiàn)有一個(gè)抽象類 PhoneManufacturer
(手機(jī)生產(chǎn)商),定義如下:
open class PhoneManufacturer(val name: String) : ScreenManufacturer, CpuManufacturer, Assembler
interface ScreenManufacturer {
fun produceScreen()
}
interface CpuManufacturer {
fun produceCpu()
}
interface Assembler {
fun assemble()
}
其中刹淌,手機(jī)生產(chǎn)商 PhoneManufacturer
繼承了三個(gè)接口饶氏,分別為 ScreenManufacturer
(屏幕生產(chǎn)商)、CpuManufacturer
(Cpu生產(chǎn)商)有勾、Assembler
(組裝廠)疹启。
但是,由于手機(jī)生產(chǎn)商能力有限蔼卡,無(wú)法將所有環(huán)節(jié)全部由自己制造喊崖,所以需要將一部分環(huán)節(jié)委托給代工廠。于是雇逞,利用委托模式荤懂,可以將 PhoneManufacturer
修改為以下形式:
open class PhoneManufacturer(
val name: String,
private val screenManufacturer: ScreenManufacturer,
private val cpuManufacturer: CpuManufacturer,
private val assembler: Assembler
) : ScreenManufacturer by screenManufacturer,
CpuManufacturer by cpuManufacturer,
Assembler by assembler {
fun sell() {
println("${name}賣出了一臺(tái)由${screenManufacturer}生產(chǎn)屏幕、${cpuManufacturer}生產(chǎn)Cpu塘砸、${assembler}組裝的手機(jī)")
}
}
于是节仿,手機(jī)生產(chǎn)商就可以將屏幕生產(chǎn)、Cpu生產(chǎn)掉蔬、組裝全部外包出去廊宪,只管銷售就行了查近。
現(xiàn)在,我們來(lái)測(cè)試一下效果挤忙。
fun main(args: Array<String>) {
val xiaomi = PhoneManufacturer("Xiaomi", Samsung(), Qualcomm(), Foxconn())
xiaomi.sell()
}
class Samsung : ScreenManufacturer {
override fun produceScreen() = Unit
override fun toString(): String = "Samsung"
}
class Qualcomm : CpuManufacturer {
override fun produceCpu() = Unit
override fun toString(): String = "Qualcomm"
}
class Foxconn : Assembler {
override fun assemble() = Unit
override fun toString() = "Foxconn"
}
在這里霜威,我們新建了三個(gè)類:三星作為屏幕生產(chǎn)商、高通作為Cpu生產(chǎn)商册烈、富士康作為組裝廠戈泼;然后創(chuàng)建手機(jī)廠商對(duì)象 xiaomi
,并調(diào)用其 sell
函數(shù)赏僧。
輸出:
Xiaomi賣出了一臺(tái)由Samsung生產(chǎn)屏幕大猛、Samsung生產(chǎn)Cpu、Foxconn組裝的手機(jī)
可以看到淀零,委托成功挽绩。也就是說(shuō),PhoneManufacturer
類雖然實(shí)現(xiàn)了上述三個(gè)接口驾中,但是并沒(méi)有自己實(shí)現(xiàn)其中的函數(shù)唉堪,而是委托給其他對(duì)象來(lái)實(shí)現(xiàn)了。這就是類委托肩民。
二唠亚、屬性委托
在開發(fā)中經(jīng)常會(huì)遇到需要懶加載的情況:某個(gè)值在類創(chuàng)建的時(shí)候無(wú)法確定,需要在使用的時(shí)候進(jìn)行處理持痰。這個(gè)時(shí)候灶搜,通常可以使用 by lazy
進(jìn)行數(shù)據(jù)的懶加載工窍。
這有點(diǎn)類似于 lateinit var
割卖,不過(guò)是在第一次調(diào)用的時(shí)候進(jìn)行數(shù)據(jù)加載。
例如患雏,在常規(guī) Android 開發(fā)中鹏溯,控件通常定義為 lateinit var
類型,并在 onCreate
回調(diào)之中進(jìn)行 findViewById
查找控件的纵苛。
private lateinit var image: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
image = findViewById(R.id.img)
}
而使用 by lazy
進(jìn)行懶加載剿涮,也是一種方式;它可以將控件的查找延后到第一次使用的時(shí)候攻人。這樣可以減少 onCreate
中要做的事取试,并且使代碼看起來(lái)更簡(jiǎn)潔。
private val image: ImageView by lazy { findViewById(R.id.img) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crop_bmp)
}
而懶加載利用到的就是 kotlin 中的屬性委托怀吻。
如果想自己實(shí)現(xiàn)一個(gè)委托瞬浓,很簡(jiǎn)單,只用創(chuàng)建一個(gè)類蓬坡,并且在其中添加一個(gè) getValue
函數(shù)返回需要的值即可猿棉。例如磅叛,我想要實(shí)現(xiàn)一個(gè) findViewById
的委托,只需要添加這么一個(gè)擴(kuò)展函數(shù)和委托類即可:
fun <T : View> Activity.findView(id: Int) = FindViewDelegate<T>(id)
class FindViewDelegate<T : View>(val id: Int) {
operator fun getValue(activity: Activity, kProperty: KProperty<*>): T {
return activity.findViewById(id)
}
}
其中萨赁,getValue
的第一個(gè)參數(shù)必須是屬性所在類的類型弊琴,在這里是 Activity
;第二個(gè)參數(shù)是 KProperty<*>
類型杖爽,對(duì)應(yīng)了需要被代理的那個(gè)屬性敲董。
然后,在任意的 Activity 中慰安,均可以使用 findView
擴(kuò)展函數(shù)進(jìn)行屬性委托了腋寨。
class MainActivity : AppCompatActivity() {
private val image: ImageView by findView(R.id.img)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_crop_bmp)
}
}
其他系統(tǒng)提供的屬性委托
kotlin.properties.Delegates
工具類提供了一些系統(tǒng)自帶的屬性委托。
1. notNull
委托于 Delegates.notNull
的變量類似于 lateinit var
化焕,只是一個(gè)類似于占位符的東西萄窜,表示這個(gè)對(duì)象不能為空,作用只是通過(guò)編譯撒桨。在實(shí)際使用之前查刻,還是需要將其進(jìn)行初始化。
var name: String by Delegates.notNull()
fun main(args: Array<String>) {
val nnd = NotNullDelegate()
nnd.name = "Bob"
println(nnd.name)
}
2. observable
Delegates.observable
用于實(shí)現(xiàn)觀察者模式元莫,監(jiān)聽(tīng)變量的修改赖阻。
var name: String by Delegates.observable("Bob") { property, oldValue, newValue ->
notifyNameChanged(property, oldValue, newValue)
}
fun notifyNameChanged(property: KProperty<*>, oldValue: String, newValue: String) {
// do something
}
3. vetoable
Delegates.vetoable
同樣可以監(jiān)聽(tīng)變量的修改,并且能夠攔截這次修改踱蠢。如果修改被攔截,則變量的值保持不變棋电。
var name: String by Delegates.vetoable("Bob") { property, oldValue, newValue ->
// 新值不能為空且不能為"Alice"茎截,否則保持原值
newValue.isNotEmpty() && newValue != "Alice"
}