原文地址:Kotlin學習快速入門(8)—— 屬性委托 - Stars-One的雜貨小窩 委托其實是一種設計模式,但Kotlin把此特性編寫進了語法中,可以方便開發者快速使用 委托對應的關鍵字是by 屬性委托 先講下屬性委托吧,首先,複習下kotlin中設置set和get方法 預設的set和get我 ...
原文地址:Kotlin學習快速入門(8)—— 屬性委托 - Stars-One的雜貨小窩
委托其實是一種設計模式,但Kotlin把此特性編寫進了語法中,可以方便開發者快速使用
委托對應的關鍵字是by
屬性委托
先講下屬性委托吧,首先,複習下kotlin中設置set和get方法
預設的set和get我們可以隱藏,實際上一個簡單的類代碼如下:
class Person {
var personName = ""
// 這是預設的 get/set(預設是隱藏的)
get() = field
set(value) {
field = value
}
}
這裡具體知識點可以查看之前所說Kotlin學習快速入門(3)——類 繼承 介面 - Stars-One的雜貨小窩
當然,如果是數據bean類,我們會將get和set方法隱藏(或者使用data
關鍵字來聲明一個數據類)
若我們需要在get或set方法的時候做一下邏輯處理,比如說上面的personName
欄位,我們只允許接收長度小於等於10的字元串,超過10長度的字元串就不接收(即不設置新數值),則是應該這樣寫:
class Person{
var personName = ""
// 這是重寫的 get/set
get() = "PersonName $field"
set(value) {
field = if (value.length <= 10) value else field
}
}
然後,我們再延伸出來,如果此規則不止應用於personName欄位,還可用到其他類的欄位中,這個時候就是使用到屬性委托。
簡單描述: 我們將此規則抽取出來,需要應用到此規則的欄位的get/set方法委托給規則去做,這就叫屬性委托
延遲載入(懶載入)
在開始講屬性委托之前,先說明下延遲載入
Kotlin中提供了lazy方法,使用by
+lazy{}
聯用,我們就實現延遲載入(也可稱作懶載入)
fun main() {
val demo = Demo()
val textContent = demo.textContent
val result = demo.textContent.substring(1)
println(result)
println("列印:$textContent")
}
class Demo{
val textContent by lazy { loadFile() }
}
fun loadFile(): String {
println("讀取文件...")
//模擬讀取文件返回數據
return "讀取的數據"
}
這裡的關鍵詞by出現在屬性名後面,表示屬性委托,即將屬性的讀和寫委托給另一個對象,被委托的對象必須滿足一定的條件:
- 對於
val
修飾的只讀變數進行屬性委托時,被委托的對象必須實現getValue()
介面,即定義如何獲取變數值。 - 對於
var
修飾的讀寫變數進行屬性委托時,被委托對象必須實現getValue()
和setValue()
介面,即定義如何讀寫變數值。
lazy()
方法,接收一個lambda函數,返回值是一個Lazy對象,所以就可以簡寫成上面的樣子,其只實現了getValue()
介面,所以,當你嘗試將textContent
改為var類型,IDE會提示報錯!!
也是因為這點,屬於延遲載入的欄位,是不可被再次修改了,所以採用lazy懶載入的方式,其實就是單例模式
Delegates.vetoable
還記得上述我們要實現的規則嗎,其實Kotlin中已經有了幾個預設的委托規則供我們快速使用(上述的lazy其實也是一個)
Delegates.vetoable()的規則就是上述規則的通用封裝,解釋為:
但會在屬性被賦新值生效之前會傳遞給Delegates.vetoable()
進行處理,依據Delegates.vetoable()
的返回的布爾值判斷要不要賦新值。
如下麵例子:
class Person {
var personName by Delegates.vetoable("") { property, oldValue, newValue ->
//當設置的新值滿足條件,則會設置為新值
newValue.length <= 10
}
}
Delegates.notNull
設置欄位不能為null,不過想不到具體的應用情景
class Person {
var personName by Delegates.notNull<String>()
}
Delegates.observable
使用Delegates.observable
可以幫我們快速實現觀察者模式,只要欄位數值發生改變,就會觸發
class Person{
var age by Delegates.observable(0){ property, oldValue, newValue ->
//這裡可以寫相關的邏輯
if (newValue >= 18) {
tip = "已成年"
}else{
tip = "未成年"
}
}
var tip =""
}
上面的例子就比較簡單,設置age同時更新提示,用來判斷是否成年
val person = Person()
person.age = 17
println(person.tip)
補充-自定義委托
上述都是官方定義好的一些情形,但如果不滿足我們的需求,這就需要自定義委托了
官方提供了兩個基礎類供我們自定義委托使用:
ReadWriteProperty
包含get和set方法,對應var
關鍵字
ReadOnlyProperty
只有get方法,對應val
關鍵字
PS:實際上,我們自己隨意創建個委托類也是可以的,不過這樣寫不太規範,所以我們一般直接實現官方給的上述兩個類即可
ReadWriteProperty和ReadOnlyProperty都需要傳兩個泛型,分別為R,T
R
持有屬性的類型T
欄位類型
可能上面描述不太明白,下麵給個簡單例子,Person類中有個name欄位(String),首字母需要大寫:
class Person {
var name by NameToUpperCase("")
}
class NameToUpperCase(var value:String) :ReadWriteProperty<Person,String>{
//NameToUpperCase類中預設有個屬性欄位,用來存數據
override fun getValue(thisRef: Person, property: KProperty<*>): String {
return this.value
}
override fun setValue(thisRef: Person, property: KProperty<*>, value: String) {
//在設置數值的時候,將第一個字母轉為大寫,一般推薦在setValue里編寫邏輯
this.value = value.substring(0,1).toUpperCase()+value.substring(1)
}
}
個人看法,一般在setValue的時候進行設置數值比較好,因為getValue作操作的話,會觸發多次,處理的邏輯複雜的話可能會浪費性能...
當然,再提醒下,上面的邏輯也可以直接去欄位里的setValue()
裡面改,委托只是方便抽取出去供其他類應用同樣的規則
PS: 如果你的委托不是針對特定的類,R泛型可以改為Any
類委托
這個一般與多態一起使用,不過個人想不到有什麼具體的應用情景,暫時做下簡單的記錄
interface IDataStorage{
fun add()
fun del()
fun query()
}
class SqliteDataStorage :IDataStorage{
override fun add() {
println("SqliteDataStorage add")
}
override fun del() {
println("SqliteDataStorage del")
}
override fun query() {
println("SqliteDataStorage query")
}
}
假如現在我們有個MyDb類,查詢的方法與SqliteDataStorage這個裡的方法有所區別,但其他方法都是沒有區別,這個時候就會用到類委托了
有以下幾種委托的使用方式
1.委托類作為構造器形參傳入(常用)
class MyDb(private val storage:IDataStorage) : IDataStorage by storage{
override fun add() {
println("mydb add")
}
}
val db = MyDb(SqliteDataStorage())
db.add()
db.query()
輸出結果:
mydb add
SqliteDataStorage query
如果是全部都是委托給SqliteDataStorage的話,可以簡寫為這樣:
class MyDb(private val storage:IDataStorage) : IDataStorage by storage
2.新建委托類對象
class MyDb : IDataStorage by SpDataStorage(){
override fun add() {
println("mydb add")
}
}
這裡測試的效果與上文一樣,不在重覆贅述
參考
- kotlin-屬性委托_南郭竽的博客-CSDN博客_kotlin屬性委托
- Kotlin 基礎 | 委托及其應用_普通網友的博客-CSDN博客
- Kotlin修煉指南(五)—Delegates_eclipse_xu的博客-CSDN博客
- kotlin類委托、屬性委托_水煮魚在飛的博客-CSDN博客_kotlin 類委托
提問之前,請先看提問須知 點擊右側圖標發起提問 或者加入QQ群一起學習 TornadoFx學習交流群:1071184701