委託,也就是委託模式,它是23種經典設計模式種的一種,又名代理模式
,在委託模式中,有2個對象參與同一個請求的處理,接受請求的對象將請求委託給另外一個對象來處理。委託模式是一項技巧,其餘的幾種設計模式如:策略模式、狀態模式和訪問者模式都是委託模式的具體場景應用。html
委託模式中,有三個角色,約束、委託對象和被委託對象。git
約束: 約束是接口或者抽象類,它定義了通用的業務類型,也就是須要被代理的業務github
被委託對象: 具體的業務邏輯執行者json
委託對象: 負責對真是角色的應用,將約束累定義的業務委託給具體的委託對象。設計模式
上一節講了委託的定義和它所包含的幾個角色,那麼具體該怎麼運用呢?咱們以一個實際的例子來看看。api
如今不少年輕人都愛完遊戲,不論是吃雞、王者榮耀仍是英雄聯盟。它們都是有等級之分的:青銅->白銀->黃金->鉑金->鑽石->宗師->王者
,等級越高,表明你越厲害,就拿英雄聯盟來講,咱們多數混跡在白銀黃金階段,要上鑽石宗師段位很是困難。好比你排位打了好久,就差幾場就能上宗師了,總是打不上去,這個時候怎麼辦呢?好辦,如今有不少遊戲代練,委託
遊戲代練給你打上去就行了。這其實就是一個委託模式。代碼該怎麼寫呢?一塊兒來看看:安全
首先,咱們定義約束類,定義咱們須要委託的業務,就拿這個場景來講,咱們的業務就是打排位賽,升級
。所以,定義個約束類(接口)IGamePlayer
:bash
// 約束類 interface IGamePlayer { // 打排位賽 fun rank() // 升級 fun upgrade() } 複製代碼
約束類中,定義了咱們要代理的業務rank()
,upgrade()
,而後,咱們就定義被委託對象
,也就是遊戲代練
:微信
// 被委託對象,本場景中的遊戲代練 class RealGamePlayer(private val name: String): IGamePlayer{ override fun rank() { println("$name 開始排位賽") } override fun upgrade() { println("$name 升級了") } } 複製代碼
如上,咱們定義了一個被委託對象RealGamePlayer
, 它有一個屬性name
,它實現了咱們約定的業務(實現了接口方法)。markdown
接下來,就是委託角色
:
// 委託對象 class DelegateGamePlayer(private val player: IGamePlayer): IGamePlayer by player 複製代碼
咱們定義了一個委託類DelegateGamePlayer
, 如今遊戲代練有不少,水平有高有低,若是發現水平不行,咱們能夠隨時換,所以,咱們把被委託對象做爲委託對象的屬性,經過構造方法傳進去。
注意:在kotlin 中,委託用關鍵字
by
修飾,by
後面就是你委託的對象,能夠是一個表達式
。所以在本例中,經過by player
委託給了具體的被委託對象。
最後,看一下場景測試類:
// Client 場景測試 fun main() { val realGamePlayer = RealGamePlayer("張三") val delegateGamePlayer = DelegateGamePlayer(realGamePlayer) delegateGamePlayer.rank() delegateGamePlayer.upgrade() } 複製代碼
咱們定義了一個遊戲代練,叫張三,將它傳遞給委託類,而後就能夠開始排位和升級的業務了,而最終誰完成了排位賽和升級了,固然是咱們的被委託對象,也就是遊戲代練--張三。
運行,結果以下:
張三 開始排位賽
張三 升級了
複製代碼
小結:以上就是委託的應用,再來回顧一下它的定義:2個對象參與處理同一請求,這個請求
就是咱們約束類的邏輯,所以委託類(DelegateGamePlayer
)和被委託類(RealGamePlayer
)都須要實現咱們的約束接口IGamePlayer
。
在Kotlin 中,有一些常見的屬性類型,雖然咱們能夠在每次須要的時候手動實現它們,可是很麻煩,各類樣板代碼存在,咱們知道,Kotlin但是宣稱要實現零樣板代碼的。爲了解決這些問題呢?Kotlin標準爲咱們提供了委託屬性
。
class Test { // 屬性委託 var prop: String by Delegate() } 複製代碼
委託屬性
的語法以下:
val/var <屬性名>: <類型> by <表達式>
跟咱們前面將的委託相似,只不過前面是類委託
,這裏屬性委託
。
前面講的委託中,咱們有個約束角色
,裏面定義了代理的業務邏輯。而委託屬性呢?其實就是上面的簡化,被代理的邏輯就是這個屬性的get
/set
方法。get
/set
會委託給被委託對象
的setValue
/getValue
方法,所以被委託類須要提供setValue
/getValue
這兩個方法。若是是val
屬性,只需提供getValue
。若是是var
屬性,則setValue
/getValue
都須要提供。
好比上面的Delegate
類:
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.") } } 複製代碼
其中的參數解釋以下:
thisRef
—— 必須與 屬性全部者 類型(對於擴展屬性——指被擴展的類型)相同或者是它的超類型;property
—— 必須是類型 KProperty<*>
或其超類型。value
—— 必須與屬性同類型或者是它的子類型。測試以下:
fun main() { println(Test().prop) Test().prop = "Hello, Android技術雜貨鋪!" } 複製代碼
打印結果以下:
Test@5197848c, thank you for delegating 'prop' to me! Hello, Android技術雜貨鋪! has been assigned to 'prop' in Test@17f052a3. 複製代碼
上面咱們講了,要實現屬性委託,就必需要提供getValue
/setValue
方法,對於比較懶
的同窗可能就要說了,這麼複雜的參數,還要每次都要手寫,真是麻煩,一不當心就寫錯了。確實是這樣,爲了解決這個問題, Kotlin 標準庫中聲明瞭2個含所需 operator
方法的 ReadOnlyProperty / ReadWriteProperty
接口。
interface ReadOnlyProperty<in R, out T> { operator fun getValue(thisRef: R, property: KProperty<*>): T } interface ReadWriteProperty<in R, T> { operator fun getValue(thisRef: R, property: KProperty<*>): T operator fun setValue(thisRef: R, property: KProperty<*>, value: T) } 複製代碼
被委託類
實現這兩個接口其中之一就能夠了,val
屬性實現ReadOnlyProperty
,var
屬性實現ReadOnlyProperty
。
// val 屬性委託實現 class Delegate1: ReadOnlyProperty<Any,String>{ override fun getValue(thisRef: Any, property: KProperty<*>): String { return "經過實現ReadOnlyProperty實現,name:${property.name}" } } // var 屬性委託實現 class Delegate2: ReadWriteProperty<Any,Int>{ override fun getValue(thisRef: Any, property: KProperty<*>): Int { return 20 } override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) { println("委託屬性爲: ${property.name} 委託值爲: $value") } } // 測試 class Test { // 屬性委託 val d1: String by Delegate1() var d2: Int by Delegate2() } 複製代碼
如上代碼所示,定義了2個屬性代理,都經過 ReadOnlyProperty / ReadWriteProperty
接口實現。
測試代碼以下:
val test = Test() println(test.d1) println(test.d2) test.d2 = 100 複製代碼
打印結果:
經過實現ReadOnlyProperty實現,name:d1 20 委託屬性爲: d2 委託值爲: 100 複製代碼
能夠看到,與手動實現setValue/getValue效果同樣,可是這樣寫代碼就方便了不少了。
Kotlin 標準庫中提供了幾種委託,例如:
lazy()
是接受一個 lambda 並返回一個 Lazy <T>
實例的函數,返回的實例能夠做爲實現延遲屬性的委託: 第一次調用 get()
會執行已傳遞給 lazy()
的 lambda 表達式並記錄結果, 後續調用 get()
只是返回記錄的結果。
val lazyProp: String by lazy { println("Hello,第一次調用纔會執行我!") "西哥!" } // 打印lazyProp 3次,查看結果 fun main() { println(lazyProp) println(lazyProp) println(lazyProp) } 複製代碼
打印結果以下:
Hello,第一次調用纔會執行我!
西哥!
西哥!
西哥!
複製代碼
能夠看到,只有第一次調用,纔會執行lambda
表達式中的邏輯,後面調用只會返回lambda
表達式的最終值。
lazy
延遲初始化是能夠接受參數的,提供了以下三個參數:
/** * Specifies how a [Lazy] instance synchronizes initialization among multiple threads. */ public enum class LazyThreadSafetyMode { /** * Locks are used to ensure that only a single thread can initialize the [Lazy] instance. */ SYNCHRONIZED, /** * Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value, * but only the first returned value will be used as the value of [Lazy] instance. */ PUBLICATION, /** * No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined. * * This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread. */ NONE, } 複製代碼
三個參數解釋以下:
LazyThreadSafetyMode.SYNCHRONIZED
: 添加同步鎖,使lazy
延遲初始化線程安全
LazyThreadSafetyMode. PUBLICATION
: 初始化的lambda
表達式能夠在同一時間被屢次調用,可是隻有第一個返回的值做爲初始化的值。
LazyThreadSafetyMode. NONE
:沒有同步鎖,多線程訪問時候,初始化的值是未知的,非線程安全,通常狀況下,不推薦使用這種方式,除非你能保證初始化和屬性始終在同一個線程
使用以下:
val lazyProp: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { println("Hello,第一次調用纔會執行我!") "西哥!" } 複製代碼
若是你指定的參數爲LazyThreadSafetyMode.SYNCHRONIZED
,則能夠省略,由於lazy
默認就是使用的LazyThreadSafetyMode.SYNCHRONIZED
。
若是你要觀察一個屬性的變化過程,那麼能夠將屬性委託給Delegates.observable
, observable
函數原型以下:
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue) } 複製代碼
接受2個參數:
initialValue
: 初始值onChange
: 屬性值被修改時的回調處理器,回調有三個參數property
,oldValue
,newValue
,分別爲: 被賦值的屬性、舊值與新值。使用以下:
var observableProp: String by Delegates.observable("默認值:xxx"){ property, oldValue, newValue -> println("property: $property: $oldValue -> $newValue ") } // 測試 fun main() { observableProp = "第一次修改值" observableProp = "第二次修改值" } 複製代碼
打印以下:
property: var observableProp: kotlin.String: 默認值:xxx -> 第一次修改值
property: var observableProp: kotlin.String: 第一次修改值 -> 第二次修改值
複製代碼
能夠看到,每一次賦值,都能觀察到值的變化過程。
vetoable
與 observable
同樣,能夠觀察屬性值的變化,不一樣的是,vetoable
能夠經過處理器函數來決定屬性值是否生效
。
來看這樣一個例子:聲明一個Int類型的屬性vetoableProp
,若是新的值比舊值大,則生效,不然不生效。
代碼以下:
var vetoableProp: Int by Delegates.vetoable(0){ _, oldValue, newValue -> // 若是新的值大於舊值,則生效 newValue > oldValue } 複製代碼
測試代碼:
fun main() { println("vetoableProp=$vetoableProp") vetoableProp = 10 println("vetoableProp=$vetoableProp") vetoableProp = 5 println("vetoableProp=$vetoableProp") vetoableProp = 100 println("vetoableProp=$vetoableProp") } 複製代碼
打印以下:
vetoableProp=0
0 -> 10
vetoableProp=10
10 -> 5
vetoableProp=10
10 -> 100
vetoableProp=100
複製代碼
能夠看到10 -> 5
的賦值沒有生效。
還有一種狀況,在一個映射(map)裏存儲屬性的值,使用映射實例自身做爲委託來實現委託屬性,如:
class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } 複製代碼
測試以下:
fun main() { val user = User(mapOf( "name" to "西哥", "age" to 25 )) println("name=${user.name} age=${user.age}") } 複製代碼
打印以下:
name=西哥 age=25
複製代碼
使用映射實例自身做爲委託來實現委託屬性,可使用在json解析中,由於json自己就能夠解析成一個map。不過,說實話,我暫時尚未發現這種使用場景的好處或者優點,若是有知道的同窗,評論區告知,謝謝!
委託在kotlin中佔有舉足輕重的地位,特別是屬性委託,lazy延遲初始化使用很是多,還有其餘一些場景,好比在咱們安卓開發中,使用屬性委託來封裝SharePreference
,大大簡化了SharePreference
的存儲和訪問。在咱們軟件開發中,始終提倡的是高內聚,低耦合。而委託,就是內聚,能夠下降耦合。另外一方面,委託的使用,也能減小不少重複的樣板代碼。
參考:www.kotlincn.net/docs/refere…
若是你喜歡個人文章,就關注下個人公衆號 Android技術雜貨鋪 、 簡書 或者Github! 微信公衆號:Android技術雜貨鋪
簡書:www.jianshu.com/u/35167a70a…
GitHub:github.com/pinguo-zhou…