前言
kotlin 作為google官方android 開發語言,大勢所趨,據傳到2018底kotlin要全面替代java在android中的地位,其實這個也不擔心畢竟kotin和java可以100%互操作。兩種語言進行編寫也是可以的。
委托模式是軟體設計模式中的一項基本技巧。在委托模式中,有兩個對象參與處理同一個請求,
接受請求的對象将請求委托給另一個對象來處理。Kotlin 直接支援委托模式,更加優雅,
簡潔。Kotlin 通過關鍵字 by 實作委托。
類委托
背景
xiaoming,xiaomingfather,xiaomingmother
小明,向他爸要錢
買玩具,其實錢是他媽管,找他爸要錢的小明,最後出自他媽手中。(呵呵~~)
package KotlinAgency.Kagency
/**
* Created by weichyang on 2017/11/2.
* 委托方
*/
interface KXiaomingFather {
fun payMoney()
}
p
ackage KotlinAgency.Kagency
import KotlinAgency.XiaomingFather
/**
* Created by weichyang on 2017/11/2
*被委托方
*/
class KxiaomingMonther(var payMoney: Int) : XiaomingFather {
override fun payMoney() {
print("給小明零花錢" + payMoney + "元")
}
}
package KotlinAgency.Kagency
import KotlinAgency.XiaomingFather
/**
* Created by weichyang on 2017/11/2.
* 在 KXiaoming 聲明中,by 子句表示,将 b 儲存在 KXiaoming 的對象執行個體内部,
* 而且編譯器将會生成繼承自 XiaomingFather 接口的所有方法, 并将調用轉發給 b。
*
* 也就是說,編譯器會生成 payMoney()方法,拿到Kxiaoming 對象後可以直接調用b中方法,也就是
* KxiaomingFather中的方法
*
*/
class KXiaoming(b: XiaomingFather) : XiaomingFather by b {
fun buyToy(): KXiaoming {
println("想要買買玩具")
return this
}
}
package KotlinAgency.Kagency
import KotlinAgency.XiaomingMother
/**
* Created by weichyang on 2017/11/2.
*/
object Java2KotlinAnency {
@JvmStatic
fun main(args: Array<String>) {
val angency = XiaomingMother(10)
KXiaoming(angency).buyToy().payMoney()
}
}
上面代碼就是java中代理寫法的Kontlin代碼。隻是寫法變了。
屬性委托
屬性委托指的是一個類的某個屬性值不是在類中直接進行定義,而是将其托付給一個代理類,進而實作對該類的屬性統一管理。
屬性委托需要提供getValue()setValue()方法的實作。Val隻需要提供getValue()方法的實作
屬性委托文法格式:
val/var <屬性名>: <類型> by <表達式>
var/val:屬性類型(可變/隻讀)
屬性名:屬性名稱
類型:屬性的資料類型
表達式:委托代理類
by
關鍵字之後的表達式就是委托, 屬性的 get() 方法(以及set() 方法)将被委托給這個對象的 getValue() 和 setValue() 方法。
屬性委托不必實作任何接口, 但必須提供 getValue() 函數(對于 var屬性,還需要 setValue() 函數)。
定義一個被委托的類
該類需要包含 getValue() 方法和 setValue() 方法,且參數 thisRef 為進行委托的類的對象,prop 為進行委托的屬性的對象。
import kotlin.reflect.KProperty
// 定義包含屬性委托的類
class Example {
var p: String by Delegate()
}
// 委托的類
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 這裡委托了 ${property.name} 屬性"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 屬性指派為 $value")
}
}
fun main(args: Array<String>) {
val e = Example()
println(e.p) // 通路該屬性,調用 getValue() 函數
e.p = "Runoob" // 調用 setValue() 函數
println(e.p)
}
輸出結果為:
Example@433c675d, 這裡委托了 p 屬性
Example@433c675d 的 p 屬性指派為 Runoob
Example@433c675d, 這裡委托了 p 屬性
标準委托
Kotlin 的标準庫中已經内置了很多工廠方法來實作屬性的委托。
延遲屬性 Lazy
lazy() 是一個函數, 接受一個 Lambda 表達式作為參數, 傳回一個 Lazy 執行個體的函數,傳回的執行個體可以作為實作延遲屬性的委托: 第一次調用 get() 會執行已傳遞給 lazy() 的 lamda 表達式并記錄結果, 後續調用 get() 隻是傳回記錄的結果。
val lazyValue: String by lazy {
println("computed!") // 第一次調用輸出,第二次調用不執行
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue) // 第一次執行,執行兩次輸出表達式
println(lazyValue) // 第二次執行,隻輸出傳回值
}
執行輸出結果:
computed!
Hello
Hello
可觀察屬性 Observable
顧名思義,可觀察屬性,就是指屬性的變化可以通過函數進行輸出
Delegates.observable() 函數接受兩個參數: 第一個是初始化值, 第二個是屬性值變化事件的響應器(handler)。
在屬性指派後會執行事件的響應器(handler),它有三個參數:被指派的屬性、舊值和新值:
以下響應器實作通過lambda函數來實作。
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("初始值") {
prop, old, new ->
println("舊值:$old -> 新值:$new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "第一次指派"
user.name = "第二次指派"
}
執行輸出結果:
舊值:初始值 -> 新值:第一次指派
舊值:第一次指派 -> 新值:第二次指派
把屬性儲存在映射中
一個常見的用例是在一個映射(map)裡存儲屬性的值。
這經常出現在像解析 JSON 或者做其他”動态”事情的應用中。
在這種情況下,你可以使用映射執行個體自身作為委托來實作委托屬性。
類似java中的Map。 注:val,var 映射方式不同,一個采用Map,一個采用mutiableMap
class Site(val map: Map<String, Any?>) {
val name: String by map
val url: String by map
}
fun main(args: Array<String>) {
// 構造函數接受一個映射參數
val site = Site(mapOf(
"name" to "菜鳥教程",
"url" to "www.runoob.com"
))
// 讀取映射值
println(site.name)
println(site.url)
}
Not Null
notNull 适用于那些無法在初始化階段就确定屬性值的場合。
class Foo {
var notNullBar: String by Delegates.notNull<String>()
}
foo.notNullBar = "bar"
println(foo.notNullBar)
局部委托屬性
你可以将局部變量聲明為委托屬性。 例如,你可以使一個局部變量惰性初始化:
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
memoizedFoo 變量隻會在第一次通路時計算。 如果 someCondition 失敗,那麼該變量根本不會計算。
屬性委托要求
對于隻讀屬性(也就是說val屬性), 它的委托必須提供一個名為getValue()的函數。
該函數接受以下參數:
thisRef —— 必須與屬性所有者類型(對于擴充屬性——指被擴充的類型)相同或者是它的超類型
property —— 必須是類型 KProperty<*> 或其超類型
這個函數必須傳回與屬性相同的類型(或其子類型)。
對于一個值可變(mutable)屬性(也就是說,var 屬性),除 getValue()函數之外,它的委托還必須 另外再提供一個名為setValue()的函數, 這個函數接受以下參數:
property —— 必須是類型 KProperty<*> 或其超類型
new value —— 必須和屬性同類型或者是它的超類型。
翻譯規則
在每個委托屬性的實作的背後,Kotlin 編譯器都會生成輔助屬性并委托給它。 例如,對于屬性 prop,生成隐藏屬性 prop$delegate,而通路器的代碼隻是簡單地委托給這個附加屬性:
class C {
var prop: Type by MyDelegate()
}
// 這段是由編譯器生成的相應代碼:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
Kotlin 編譯器在參數中提供了關于 prop 的所有必要資訊:第一個參數 this 引用到外部類 C 的執行個體而 this::prop 是KProperty 類型的反射對象,該對象描述 prop 自身。
提供委托
import kotlin.reflect.KProperty
/**
* 就是給A和委托B之間插入一個中間件而已
* 按照正常屬性委托流程,需要自己根據val ,var實作getValue(),或者setValue(),提供
*委托實在屬性委托的基礎上增加一個 provideDelegate()成員函數或者擴充函數作為中間件,進行一些操作
*來實作,感覺有點類似,面向切面程式設計的切面效果。
*/
class dge<T>(t: T) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 這裡委托了 ${property.name} 屬性"
}
}
class ResourceLoader<T>(id: Int) {
//可以擴充建立屬性實作所委托對象的邏輯
operator fun provideDelegate(
thisRef: MyUI,
prop: KProperty<*>): dge<T?> {
checkProperty(thisRef, prop.name)
// 建立委托
var t: T? = null
return dge(t)
}
private fun checkProperty(thisRef: MyUI, name: String) {
println(name)
}
}
fun <T> bindResource(id: Int): ResourceLoader<T> {
return ResourceLoader<T>(id)
}
class MyUI {
//被委托類.委托類,委托
//将 image 和 text的 get,set屬性 委托,委托類個getValue( )
val image by ResourceLoader<Int>(1) //兩個屬性進行了委托、實作getValue()
val text by ResourceLoader<Int>(2)
}
fun main(args: Array<String>) {
val myui: MyUI = MyUI()
println(myui.image)
}