美文网首页我的Kotlin之旅Android开发经验谈Android开发
Kotlin学习(十八): 委托模式(Delegate)和委托属

Kotlin学习(十八): 委托模式(Delegate)和委托属

作者: 叫我旺仔 | 来源:发表于2017-11-26 20:47 被阅读640次
    Tooling

    委托模式已经被证明是实现继承的一个很好的替代方式,在扩展一个基类并且重写方法时,基类就必须依赖子类的实现,当不断地修改的时候,基类就会失去当初的性质,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
    

    这么简单?就能实现那几个方法?我们来看一下生成的代码

    image

    是不是省去了很多手写的代码量,下面我们来介绍这种属性。

    委托模式(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)
        }
    }
    

    是不是省去很多无用的代码,只需要重写我们需要的方法addaddAll,其他没有写出来的方法全部都交给委托来实现。

    而且没有对底层集合的实现方法引入任何的依赖,所以对被调用的操作具有完全的控制,如不用担心集合是不是通过循环中调用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的的实例时,DeletagegetValue就会被调用,getValue函数中的第一个参数就是p获取到的实例,第二个参数就是属性p

    val e = Example()
    println(e.p)
    

    打印出来的内容是:

    Example@33a17727, thank you for delegating ‘p’ to me!
    

    由于pvar类型的,所有可以调用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

    image

    函数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
    打印的内容:

    image

    那么如果我们用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省去了很多多余的代码。

    image

    默认地,对于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.Delegates
    
    class 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> -> first
    first -> second
    

    如果需要拦截修改属性值动作并禁止修改,可以使用vetoable()取代observable()handler需要返回一个Booleantrue表示同意修改,false表示禁止修改,该回调会在属性值修改前调用。
    将上面的例子里面的observable()修改为vetoable()后:

    import kotlin.properties.Delegates
    
    class 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"
    }
    

    打印的结果是:

    false

    如果改为true,那就跟observable()打印的一样了:

    true

    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关键字修饰。

    接口

    委托类可以实现ReadOnlyProperyReadWriteProperty接口中的带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为提供委托,它可以为属性提供对象委托逻辑,可能的使用场景是在创建属性时(而不仅在其 gettersetter 中)检查属性一致性。
    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)和委托属

        本文链接:https://www.haomeiwen.com/subject/mwufgttx.html