代理模式已經(jīng)被證明是實(shí)現(xiàn)繼承的一個優(yōu)秀的替代方式,而Kotlin的Delegation語法方便地實(shí)現(xiàn)了代理模式该镣,而不需要任何模板冻璃。
代理模式
在了解Delegation之前,有必要先復(fù)習(xí)一下代理模式损合,回顧一下它的使用場景省艳。
不清楚的讀者可以移步這篇文章。
這里我要重點(diǎn)引用這篇文章對于應(yīng)用場景的總結(jié):
遠(yuǎn)程代理:為一個對象在不同的地址空間提供局部代表嫁审,這樣系統(tǒng)可以將Server部分的事項(xiàng)隱藏跋炕。
虛擬代理:使用一個代理對象表示一個十分耗資源的對象并在真正需要時才創(chuàng)建。
安全代理:用來控制真實(shí)對象訪問時的權(quán)限律适。
智能指引:當(dāng)調(diào)用真實(shí)的對象時辐烂,代理處理另外一些事,比如計(jì)算真實(shí)對象的引用計(jì)數(shù)捂贿,當(dāng)該對象沒有引用時纠修,可以自動釋放它;或者訪問一個實(shí)際對象時厂僧,檢查是否已經(jīng)能夠鎖定它扣草,以確保其他對象不能改變它。
Class Delegation
官方文檔給了我們這樣一個例子:
interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // prints 10 }
這里通過短句by確定了b這個動態(tài)代理,b作為Derived類的對象德召,編譯器會為它生成所有Base的接口方法白魂。
然后在真正需要代理的時候,把被代理的類的實(shí)例作為參數(shù)來實(shí)例化代理類上岗,然后調(diào)用接口方法福荸,則可以實(shí)現(xiàn)動態(tài)代理。
這個顯然比Java利用反射來實(shí)現(xiàn)代理要方便得多肴掷。
Delegated Properties
有一些屬性敬锐,我們每次需要的時候可以實(shí)現(xiàn)他們,但是有種方法可以只需要實(shí)現(xiàn)一次呆瞻。有這樣幾個場景:
- lazy properties: 只有第一次訪問時才需要計(jì)算的屬性
- observable properties: 監(jiān)聽變化的屬性
- 把屬性存在一個map里台夺,而不是分散的feild
為了滿足以上需求,Kotlin推出了代理屬性delegate properties:
class Example {
var p: String by Delegate()
}
與成員p對應(yīng)的get和set方法都會被Delegate的get和set方法所代理痴脾。Delegate不需要實(shí)現(xiàn)什么接口颤介,但是必須提供get方法,如果是var類型的赞赖,還必須提供set方法滚朵。eg:
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.'")
}
}
現(xiàn)在屬性p就變成了Delegate的一個實(shí)例,讀取p就會調(diào)用Delegate的getValue方法,第一個參數(shù)表示代理p屬性所在的類的實(shí)例,第二個參數(shù)則是p屬性本身先馆。例如:
val e = Example()
println(e.p) // will print "Example@33a17727, thank you for delegating ‘p’ to me!"
e.p = "NEW" // will print "NEW has been assigned to ‘p’ in Example@33a17727."
值得注意的是蛉顽,getValue和setValue方法前必須添加operater,這是因?yàn)镈elegate類實(shí)際是實(shí)現(xiàn)了系統(tǒng)標(biāo)準(zhǔn)庫的接口,所以必須保持一致。
標(biāo)準(zhǔn)代理庫
Kotlin標(biāo)準(zhǔn)庫提供了幾個常用的標(biāo)準(zhǔn)代理。老實(shí)講漏峰,除了lazy目前其他的靈活代理我還沒有發(fā)現(xiàn)使用場景。發(fā)現(xiàn)了一定回來做補(bǔ)充变丧。
lazy
lazy是一個方法芽狗。它可以通過傳入一個lamda函數(shù)返回一個Lazy<T>實(shí)例用于代理屬性。第一次get調(diào)用痒蓬,會執(zhí)行傳入lazy()的lamda函數(shù)童擎,并且記錄返回值,后續(xù)的調(diào)用只會返回第一次記錄的值攻晒。
例如:
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}
打印結(jié)果是這樣的:
computed!
Hello
Hello
如果你想要線程安全顾复,使用 blockingLazy(): 它還是按照同樣的方式工作,但保證了它的值只會在一個線程中計(jì)算鲁捏,并且所有的線程都獲取的同一個值芯砸。
用途:在我們生命類的成員的時候,很多時候還不需要初始化,這時假丧,我們就可以用以初始化的構(gòu)造函數(shù)作為lazy的參數(shù)双揪,然后形成代理屬性。比如:
private val bannerAdapter: BannerAdapter by lazy { BannerAdapter() }
val viewPager: ViewPager by lazy { ViewPager(context) }
private val indicators: LinearLayout by lazy { LinearLayout(context) }
private val tvTitle: JumpShowTextView by lazy { JumpShowTextView(context) }
private val tvSlogan: JumpShowTextView by lazy { JumpShowTextView(context) }
observable
Delegates.observable() 有兩個參數(shù):初始值和用于修改的handler包帚。每次給這個屬性派值(生效)的時候渔期,handler都會被調(diào)用。這個Handler有三個參數(shù):被指派的屬性渴邦,舊值和新值:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
這個例子打印如下:
<no name> -> first
first -> second
map
Delegates.mapVal() 擁有一個 map 實(shí)例并返回一個可以從 map 中讀其中屬性的代理疯趟。在應(yīng)用中有很多這樣的例子,比如解析 JSON 或者做其它的一些 "動態(tài)"的事情:
class User(val map: Map<String, Any?>) {
val name: String by Delegates.mapVal(map)
val age: Int by Delegates.mapVal(map)
}
在這個例子中谋梭,構(gòu)造函數(shù)持有一個 map :
val user = User(mapOf (
"name" to "John Doe",
"age" to 25
))
代理從這個 map 中取指(通過屬性的名字):
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
var 可以用 mapVar