Kotlin学习(十八): 委托模式(Delegate)和委托属性(Delegate Properties)
来源:互联网 发布:dh密钥交换算法 编辑:程序博客网 时间:2024/06/06 10:07
委托模式已经被证明是实现继承的一个很好的替代方式,在扩展一个基类并且重写方法时,基类就必须依赖子类的实现,当不断地修改的时候,基类就会失去当初的性质,Kotlin中就将类默认为final
,确保不会被修改。
有一种模式是装饰器模式,本质就是创建一个新类,实现与基类一样的接口,并且将类的实现作为一个字段保存,这样就能在基类不被修改就能直接修改基类的实例。但是这样的缺点是会造成很多的样板代码。
class DelegatingCollection<T> : Collection<T> { private val innerList = mutableListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.contains(element) override fun containsAll(elements: Collection<T>): Boolean = innerList.addAll(elements) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator()}
当你实现Collection
接口的时候,需要重写这几个方法,这里面的代码量是很多的,但是如果用了委托,那么代码就是这样的
class DelegatingCollection2<T>(innerList: Collection<T> = mutableListOf<T>()) : Collection<T> by innerList
这么简单?就能实现那几个方法?我们来看一下生成的代码
是不是省去了很多手写的代码量,下面我们来介绍这种属性。
委托模式(Delegate)
Kotlin支持委托模式,是允许对象组合实现与继承相同的代码复用的,简单来说就是操作的对象不用自己去执行,而是将任务交给另一个对象操作,这样的模式就叫委托模式,被操作的对象叫委托。
委托模式是有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
类委托
同样我们用上面集合的栗子,现在我们已经是委托到一个对象了,如果我们要修改集合里面的方法的时候,可以直接重写,而不用重复的去写新的方法,下面我们来在一个集合里面插入数据,并且获取插入的次数。
下面代码是默认实现MutableCollection
接口,获取插入的次数
class DefaultCollection<T> : MutableCollection<T> { private val innerList = mutableListOf<T>() private var addedSum = 0 override fun add(element: T): Boolean { addedSum++ return innerList.add(element) } override fun addAll(elements: Collection<T>): Boolean { addedSum += elements.size return innerList.addAll(elements) } override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.contains(element) override fun containsAll(elements: Collection<T>): Boolean = innerList.addAll(elements) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): MutableIterator<T> = innerList.iterator() override fun clear() = innerList.clear() override fun remove(element: T): Boolean = innerList.remove(element) override fun removeAll(elements: Collection<T>): Boolean = innerList.removeAll(elements) override fun retainAll(elements: Collection<T>): Boolean { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. }}
实现类委托
class DelegatingCollection3<T>(private val innerList: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerList { private var addedSum = 0 override fun add(element: T): Boolean { addedSum++ return innerList.add(element) } override fun addAll(elements: Collection<T>): Boolean { addedSum += elements.size return innerList.addAll(elements) }}
是不是省去很多无用的代码,只需要重写我们需要的方法add
和addAll
,其他没有写出来的方法全部都交给委托来实现。
而且没有对底层集合的实现方法引入任何的依赖,所以对被调用的操作具有完全的控制,如不用担心集合是不是通过循环中调用add
来实现addAll
。
委托属性(Delegate Properties)
有一种属性,在使用的时候每次都要手动实现它,但是可以做到只实现一次,并且放到库中,一直使用,这种属性称为委托属性。
委托属性包括:
- 延迟属性(lazy properties):数据只在第一次被访问的时候计算。
- 可观察属性(observable properties):监听得到属性变化通知。
- Map
委托属性(Storing Properties in a Map):将所有属性存在Map
中。
class Foo { var p: String by Delegate()}
委托模式的语法是val/var <property name>: <Type> by <expression>
在by
后面的expression
就是委托的部分,会将属性的get()
和set()
委托给getValue()
和setValue()
方法。
class Foo { private val delegate = Delegate() var p: String set(value: String) = delegate.setValue(..., value) get() = delegate.getValue(...)}
委托属性不需要实现任何的接口,但是要提供getValue()
方法(如果是var
的话要提供setValue()
方法),方法前加operator
关键字。
下面是一个自定义的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.'") }}
当从委托属性p
获取到Deletage
的的实例时,Deletage
的getValue
就会被调用,getValue
函数中的第一个参数就是p
获取到的实例,第二个参数就是属性p
。
val e = Example()println(e.p)
打印出来的内容是:
Example@33a17727, thank you for delegating ‘p’ to me!
由于p
是var
类型的,所有可以调用setValue
函数,前两个参数与getValue
参数一样,第三个就是要赋予的值:
e.p = "NEW"
这下打印出来的内容就是:
NEW has been assigned to ‘p’ in Example@33a17727.
注意:自 Kotlin1.1起可以在函数或代码块中声明一个委托属性,因此委托属性不一定是类的成员
委托标准
Kotlin的标准库中对于一些有用的委托提供了工厂(Factory)方法,这些接口在Kotlin标准库中声明。
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)}
延迟属性 Lazy
函数lazy()
接收一个Lambdas表达式,然后并返回一个Lazy <T>
的实例,它可以作为实现lazy
属性的委托:第一次调用get()
函数的时候会向执行Lambdas传递到lazy()
函数,并且保存结果,后面调用的get()
函数会直接返回报错的结果。
首先我们来看一个栗子,里面先不用到lazy
,我们来看如何
fun loadName(person: Person): String { println("Load Name for ${person.name}") return person.name}fun loadAge(person: Person): Int { println("Load Age for ${person.age}") return person.age}class Person(val name: String, val age: Int) { private var _names: String? = null val newName: String get() { if (_names == null) { _names = loadName(this) } return _names!! } private var _ages: Int? = null val newAge: Int get() { if (_ages == null) { _ages = loadAge(this) } return _ages!! }}fun main(args: Array<String>) { val p = Person("Alice", 23) p.newName.println() p.newName.println()}
首先先判断_names
是否为空,然后通过一个方法,里面进行了一些操作,来赋予_names
值,最后newName
的值即为_names
。
打印的内容:
那么如果我们用lazy
来代替这种写法会是什么样的呢?
fun loadName(person: Person): String { /*代码与上面一样*/}fun loadAge(person: Person): Int { /*代码与上面一样*/}class Person(val name: String) { val newName by lazy { loadName(this) } val newAge by lazy { loadAge(this) }}fun main(args: Array<String>) { /*代码与上面一样*/}
打印出来的内容,和上面是一模一样的。
对比一下,当我们的属性越来越多,那么重复的代码也就越来越多,使用lazy
省去了很多多余的代码。
默认地,对于lazy
属性的计算是加了同步锁(synchronized
) 的: 这个值只在一个线程被计算,并且所有的线程会看到相同的值。
如果要将同步锁关闭,可以多个线程同步执行,就加LazyThreadSafetyMode.PUBLICATION
参数即可:
val lazyValue: String by lazy(LazyThreadSafetyMode.PUBLICATION) { println("computed!") "Hello"}
如果要关掉线程安全配置,就加LazyThreadSafetyMode.NONE
参数即可:
val lazyValue: String by lazy(LazyThreadSafetyMode.NONE) { println("computed!") "Hello"}
可观察属性 Observable
Delegates.observable()
有两个参数:初始化值和handler
,每次对属性赋值操作,都会回调该handler方法(在属性赋值后执行),该方法里面有三个参数,分别是:被赋值的属性,旧值和新值。
举个例子:
import kotlin.properties.Delegatesclass 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"}
在User
类中,<no name>
就是初始化值,{}
包住的代码块就是handler
方法,pro, old, new
就是该方法的三个参数,打印出来的内容为:
<no name> -> firstfirst -> second
如果需要拦截修改属性值动作并禁止修改,可以使用vetoable()
取代observable()
,handler
需要返回一个Boolean
,true
表示同意修改,false
表示禁止修改,该回调会在属性值修改前调用。
将上面的例子里面的observable()
修改为vetoable()
后:
import kotlin.properties.Delegatesclass User { var name: String by Delegates.vetoable("<no name>") { prop, old, new -> println("$old -> $new") false }}fun main(args: Array<String>) { val user = User() user.name = "first" user.name = "second"}
打印的结果是:
如果改为true
,那就跟observable()
打印的一样了:
Map
委托属性(Storing Properties in a Map)
可以用Map
作为委托用于委托属性,多用于JSON
解析上。
下面举个栗子,一个类里面有一个Map
存放一些属性,通过setAttribute
来设置这些属性:
class Person { private val _attributes = hashMapOf<String, String>() fun setAttribute(attrName: String, value: String) { _attributes[attrName] = value } // 获取键值为name的值 val name: String get() = _attributes["name"]!! // 获取键值为company的值 val company: String get() = _attributes["company"]!! // 获取键值为address的值 val address: String get() = _attributes["address"]!! // 获取键值为email的值 val email: String get() = _attributes["email"]!!}fun main(args: Array<String>) { val p = Person() val data = mapOf("name" to "Dmitry", "company" to "JetBrains") for ((attrName, value) in data) p.setAttribute(attrName, value) println(p.name) // 打印Dmitry}
然后我们将这些属性委托给Map
,再将代码简写一下
class Person2(private val attributes: Map<String, String>) { val name: String by attributes val company: String by attributes val address: String by attributes val email: String by attributes}fun main(args: Array<String>) { val data = mapOf("name" to "Dmitry", "company" to "JetBrains") val p = Person2(data) println(p.name) // Dmitry}
同样对比,省去重复代码,我们来看一下生成的代码是什么样子的:
public final class Person2 { // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person2.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person2.class), "company", "getCompany()Ljava/lang/String;")), (KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person2.class), "address", "getAddress()Ljava/lang/String;")), (KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person2.class), "email", "getEmail()Ljava/lang/String;"))}; @NotNull private final Map name$delegate; @NotNull private final Map company$delegate; @NotNull private final Map address$delegate; @NotNull private final Map email$delegate; private final Map attributes; @NotNull public final String getName() { Map var1 = this.name$delegate; KProperty var3 = $$delegatedProperties[0]; return (String)MapsKt.getOrImplicitDefaultNullable(var1, var3.getName()); } @NotNull public final String getCompany() { Map var1 = this.company$delegate; KProperty var3 = $$delegatedProperties[1]; return (String)MapsKt.getOrImplicitDefaultNullable(var1, var3.getName()); } @NotNull public final String getAddress() { Map var1 = this.address$delegate; KProperty var3 = $$delegatedProperties[2]; return (String)MapsKt.getOrImplicitDefaultNullable(var1, var3.getName()); } @NotNull public final String getEmail() { Map var1 = this.email$delegate; KProperty var3 = $$delegatedProperties[3]; return (String)MapsKt.getOrImplicitDefaultNullable(var1, var3.getName()); } public Person2(@NotNull Map attributes) { Intrinsics.checkParameterIsNotNull(attributes, "attributes"); super(); this.attributes = attributes; this.name$delegate = this.attributes; this.company$delegate = this.attributes; this.address$delegate = this.attributes; this.email$delegate = this.attributes; }}
局部委托属性(1.1 起)
fun main(args: Array<String>) { example("localDelegate")}fun example(value: String) { val localDelegate by lazy { print("first ") value } localDelegate.println() // 打印first localDelegate localDelegate.println() // 打印localDelegate}
现在,Kotlin支持局部委托属性。
委托属性要求
下面的代码是个委托类Delegate
,里面有两个函数,一个是getValue
函数,一个是setValue
函数。
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.'") }}
只读属性(read-only
,使用val
定义)
委托类需提供getValue
函数,参数要求:
- thisRef
:第一个参数,必须是属性对应的类或父类型。(上面的thisRef: Any?
)
- property
:第二个参数,必须是“KProperty<*>”或它的父类型(上面的property: KProperty<*>
)。
- 函数返回类型必须跟属性同类型(或者子类型)。
可变属性(mutable
,使用var
定义)
委托类需提供getValue
函数和setValue
函数,参数要求:
- thisRef
:第一个参数,同getValue
对应的参数
- property
:第二个参数,同getValue
对应的参数
- 新增(new value
):第三个参数,类型必须跟属性一样或其父类型。
getValue()
和setValue()
函数可以作为委托类的成员函数或者扩展函数来使用。 当需要委托一个属性给一个不是原来就提供这些函数的对象的时候,后者更为方便。
两种函数都需要用
operator
关键字修饰。
接口
委托类可以实现ReadOnlyPropery
和ReadWriteProperty
接口中的带operator
的方法,这些接口在Kotlin标准库中声明:
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)}
提供委托(自 1.1 起)
provideDelegate
为提供委托,它可以为属性提供对象委托逻辑,可能的使用场景是在创建属性时(而不仅在其 getter
或 setter
中)检查属性一致性。 provideDelegate
的参数与 getValue
相同:
- thisRef
—— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型。
- property
—— 必须是类型 KProperty<*>
或其超类。
例如,在绑定之前检查属性名称:
class ResourceLoader(resId: ResourceID) { operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, String> { checkProperty(thisRef, prop.name) // 创建委托 } private fun checkProperty(thisRef: MyUI, name: String) { }}fun MyUI.bindResource(id: ResourceID): ResourceLoader { return ResourceLoader(id)}class MyUI { val image by bindResource(ResourceID.image_id) //bindResource()产生委托对象 val text by bindResource(ResourceID.text_id)}
在创建 MyUI
实例期间,为每个属性调用provideDelegate
方法,并立即执行必要的验证。
如果没有这种拦截属性与其委托之间的绑定的能力,为了实现相同的功能, 你必须显式传递属性名:
class MyUI { val image by bindResource(ResourceID.image_id, "image") val text by bindResource(ResourceID.text_id, "text")}fun <T> MyUI.bindResource( id: ResourceID<T>, propertyName: String ): ReadOnlyProperty<MyUI, T> { checkProperty(this, propertyName) // 创建委托}
由于暂时没用的这个提供委托,所以在这里也不过多的介绍,上面是官网的一个栗子。
一个委托实例
下面来看一个自定义的Delegate
,用来访问SharedPreference
,这段代码是Kotlin for Android Developer
的示例:
class Preference<T>(val context: Context, val name: String, val default: T) : ReadWriteProperty<Any?, T> { val prefs by lazy { context.getSharedPreferences("default", Context.MODE_PRIVATE) } override fun getValue(thisRef: Any?, property: KProperty<*>): T { return findPreference(name, default) } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { putPreference(name, value) } private fun <U> findPreference(name: String, default: U): U = with(prefs) { val res: Any = when (default) { is Long -> getLong(name, default) is String -> getString(name, default) is Int -> getInt(name, default) is Boolean -> getBoolean(name, default) is Float -> getFloat(name, default) else -> throw IllegalArgumentException("This type can be saved into Preferences") } res as U } private fun <U> putPreference(name: String, value: U) = with(prefs.edit()) { when (value) { is Long -> putLong(name, value) is String -> putString(name, value) is Int -> putInt(name, value) is Boolean -> putBoolean(name, value) is Float -> putFloat(name, value) else -> throw IllegalArgumentException("This type can be saved into Preferences") }.apply() }}
使用的时候:
class ExampleActivity : AppCompatActivity(){ var a: Int by Preference(this, "a", 0) fun whatever(){ println(a)//会从SharedPreference取这个数据 aInt = 9 //会将这个数据写入SharedPreference }}
这样就很方便了,再也不用去重复写getSharedPreference()
、commit()
、edit()
、apply()
之类的东西了。
- Kotlin学习(十八): 委托模式(Delegate)和委托属性(Delegate Properties)
- 委托(delegate)模式
- 委托(Delegate)
- 委托(Delegate)
- 委托(Delegate)
- 委托(Delegate)
- 委托(Delegate)
- 委托(Delegate)
- 委托(Delegate)
- 什么是委托(Delegate)
- 什么是委托(Delegate)
- 委托(Delegate)机制
- 委托(delegate)
- 委托(Delegate)
- 委托(Delegate)简介
- C# 委托(Delegate)
- C# 委托(Delegate)
- C# 委托(Delegate)
- openGL可编程笔记四——支持多重纹理并封装
- PTA情人节
- javascript两个数组合并及判断数据类型的方法
- Linux命令之rm
- web 环境搭建
- Kotlin学习(十八): 委托模式(Delegate)和委托属性(Delegate Properties)
- Leetcode | Number of Longest Increasing Subsequence
- Qt小程序(一)-添加资源文件
- RAID常见类型介绍
- 勾股数组(毕达哥拉斯数组)
- Activity启动模式总结
- [USACO1.2]挤牛奶Milking Cows 差分
- 319. Bulb Switcher解题报告
- The-social-network