委托是常見的模式,它和編程語言無關,即把本來自己做的事情委托給另一個物件去做,裝飾者模式和代理模式都通過委托復用了行為,Kotlin 在語言層面支持了委托,這一篇結合實體介紹一下 Kotlin 的委托,
Kotlin 的裝飾者模式
裝飾者模式和繼承擁有相同的目的,都是為了擴展類,只不過它運用了更復雜的方式通:繼承 + 組合,裝飾者模式在復用原有型別和行為的基礎上為其擴展功能,
下面是裝飾者模式的實體:
interface Accessory {
fun name(): String // 配件名字
fun cost(): Int // 配件價格
fun type(): String // 配件類別
}
這個介面用來描述一個抽象的配件,一個具體的配件需要實作三個方法,分別來定義配件名字、價格、類別,
羽毛、戒指、耳環是3個具體的配件,它的實作如下:
class Feather: Accessory{
override fun name(): String = "Feather"
override fun cost(): Int = 20
override fun type(): String = "body accessory"
}
class Ring: Accessory{
override fun name(): String = "Ring"
override fun cost(): Int = 30
override fun type(): String = "body accessory"
}
class Earrings: Accessory{
override fun name(): String = "Earrings"
override fun cost(): Int = 15
override fun type(): String = "body accessory"
}
現需要新增羽毛戒指和羽毛耳環,按照繼承的思想可以這樣實作:
class FeatherRing: Accessory{
override fun name(): String = "FeatherRing"
override fun cost(): Int = 35
override fun type(): String = "body accessory"
}
class FeatherEarrings: Accessory{
override fun name(): String = "FeatherEarrings"
override fun cost(): Int = 45
override fun type(): String = "body accessory"
}
這樣寫的缺點是只復用了型別,沒復用行為,每次新增型別的時候都得新增一個子類,會造成子類膨脹,若改用裝飾者模式,則可以減少一個子類:
class Feather(private var accessory: Accessory) : Accessory {
override fun name(): String = "Feather" + accessory.name()
override fun cost(): Int = 20 + accessory.cost()
override fun type(): String = accessory.type()
}
現在羽毛戒指和耳環分別可以這樣表達Feather(Ring())、Feather(Earrings()),
Feather運用組合持有了一個抽象的配件,這樣被注入配件的行為就得以復用,name()和cost()在復用行為的基礎上追加了新的功能,而type()直接將實作委托給了accessory,
運用 Kotlin 的委托語法可以進一步簡化Feather類:
class Feather(private var accessory: Accessory): Accessory by accessory {
override fun name(): String = "Feather" + accessory.name()
override fun cost(): Int = 20 + accessory.cost()
}
by 關鍵詞出現在類名后面,表示類委托,即把類的實作委托一個物件,該物件必須實作和類相同的介面,在這里是Accessory介面,使用by的好處是消滅模板代碼,就如上面所示,type()介面的實作就可以省略,
惰性初始化一次
惰性初始化也是一種常見的模式:延遲物件的初始化,直到第一次訪問它,當初始化消耗大量資源,惰性初始化顯得特別有價值,
支持屬性是一種實作惰性初始化的慣用技術:
class BitmapManager {
// 支持屬性用于存盤一組 Bitmap
private var _bitmaps: List<Bitmap>? = null
// 供外部訪問的一組 Bitmap
val bitmaps: List<Bitmap>
get() {
if (_bitmaps == null) {
_bitmaps = loadBitmaps()
}
return _bitmaps!!
}
}
支持屬性_bitmaps是私有的,它用來存盤一組 Bitmap,而另一個同樣型別的bitmaps用來提供一組 Bitmap 的訪問,
這樣只有當第一次訪問BitmapManager.bitmaps時,才會去加載 Bitmap,第二次訪問時,也不會重新加載 Bitmap,可直接回傳_bitmap,
上面這段代碼就是 Kotlin 預定義函式lazy()內部運用的技術,有了它就可以消滅模板代碼:
class BitmapManager {
val bitmaps by lazy { loadBitmaps() }
}
這里的關鍵詞by出現在屬性名后面,表示屬性委托,即將屬性的讀和寫委托給另一個物件,被委托的物件必須滿足一定的條件:
- 對于 val 修飾的只讀變數進行屬性委托時,被委托的物件必須實作
getValue()介面,即定義如何獲取變數值, - 對于 var 修飾的讀寫變數進行屬性委托時,被委托物件必須實作
getValue()和setValue()介面,即定義如何讀寫變數值,
屬性委托的三種實作方式
lazy()方法的回傳值是一個Lazy物件:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
public interface Lazy<out T> {
public val value: T
public fun isInitialized(): Boolean
}
Lazy類并沒有直接實作getValue()方法,它使用了另一種更加靈活的方式:
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
getValue()被宣告為Lazy類的擴展函式,這是 Kotlin 獨有的在類體外為類新增功能的特性,在原有類不能被修改的時候,特別好用,
除了擴展函式,還有另外兩種方式可以實作被委托類(假設代理的型別為 String):
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Delegate"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
}
}
這種方式新建了一個代理類,并且在類中通過關鍵詞operator多載了getValue()和setValue()這兩個運算子,分別對應取值和設定操作,
最后一種方式如下(假設代理的型別為 String):
class Delegate : ReadWriteProperty<Any?, String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Delegate"
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
}
}
即實作ReadWriteProperty介面中的getValue()和setValue()方法,
然后就可以像這樣使用代理類:
class Test {
var str: String by Delegate()
}
屬性委托背后的實作如下:
class Test {
private delegate = Delegate()
var str : String
get () = delegate.getValue(this, kProperty)
set (value: String) = delegate.setValue(this, kProperty, value)
}
新建的Delegate類會被存盤到一個支持屬性delegate中,委托屬性的設定和取值方法的實作全權委托給代理類,
委托之后,當訪問委托屬性時就好比在呼叫代理類的方法:
val test = Text()
val str = test.str // 等價于 val str = test.delegate.getValue(test, kProperty)
val test.str = str // 等價于 test.delegate.setValue(test, Kproperty, str)
委托應用
1. 更簡便地獲取傳參
委托可以隱藏細節,特別是當細節是一些模板代碼的時候:
class TestFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val id = arguments?.getString("id") ?: ""
}
}
class KotlinActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val id = intent?.getStringExtra("id") ?: ""
}
}
獲取傳遞給 Activity 或 Fragment 值的代碼就很模板,可以使用委托隱藏一下細節:
// 新建 Extras 類作為被委托類
class Extras<out T>(private val key: String, private val default: T) {
// 多載取值運算子
operator fun getValue(thisRef: Any, kProperty: KProperty<*>): T? =
when (thisRef) {
// 獲取傳遞給 Activity 的引數
is Activity -> { thisRef.intent?.extras?.get(key) as? T ?: default }
// 獲取傳遞給 Fragment 的引數
is Fragment -> { thisRef.arguments?.get(key) as? T ?: default }
else -> default
}
}
然后就可以像這樣使用委托:
class TestActivity : AppCompatActivity() {
private val id by Extras("id","0")
}
class TestFragment : Fragment() {
private val id by Extras("id","0")
}
復制代碼
2. 更簡便地獲取 map 值
有些類的屬性不是固定的,而是有時多,有時少,即動態的,比如:
class Person {
private val attrs = hashMapOf<String, Any>()
fun setAttrs( key: String, value: Any){
attrs[key] = value
}
val name: String
get() = attrs["name"]
}
有些Person有孩子,有些沒有,所以不同Person實體擁有的屬性集是不同的,這種場景用Map來存盤屬性就很合適,
上述代碼可以用委托簡化:
class Person {
private val attrs = hashMapOf<String, Any>()
fun setAttrs( key: String, value: Any){
attrs[key] = value
}
val name: String by attrs
}
將name的獲取委托給一個 map 物件,神奇之處在于,甚至都不需要指定key就可以正確地從 map 中獲取 name 屬性值,這是因為 Kotlin 標準庫已經為 Map 定義了getValue()和setValue()擴展函式,屬性名將自動作用于 map 的鍵,
總結
- Kotlin 委托分為類委托和屬性委托,它們都通過關鍵詞
by來進行委托, - 類委托可以用簡潔的語法將類的實作委托給另一個物件,以減少模板代碼,
- 屬性委托可以將對屬性的訪問委托給另一個物件,以減少模板代碼并隱藏訪問細節,
- 屬性委托有三種實作方式,分別是擴展方法、實作
ReadWriteProperty介面、多載運算子
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/384310.html
標籤:其他
