抬头仰望星空,是否能发现自己的渺小。

伪斜杠青年

人们总是混淆了欲望和理想

Kotlin by 关键字以及其应用

委托的概念

有时候,完成一些工作的方法是将它们委托给别人。这里不是在建议您将自己的工作委托给朋友去做,而是在说将一个对象的工作委托给另一个对象。

委托 (Delegation) 是一种设计模式,在该模式中,对象会委托一个助手 (helper) 对象来处理请求,这个助手对象被称为代理。代理负责代表原始对象处理请求,并使结果可用于原始对象。

委托在软件设计中扮演的角色

有人说委托就是代理,而委托真的就等于代理吗?很显然不是,让我们来对比下代理和委托。

代理设计模式(Proxy)为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

例子:

internal interface Image {
    fun displayImage()
}

class RealImageProxy(private val filename: String) : Image {
    override fun displayImage() {
        println("Displaying $filename")
    }
}

class ProxyImage(private val filename: String) : Image {
    private var image: Image? = null
    override fun displayImage() {
        if (image == null) image = RealImageProxy(filename)
        image?.displayImage()
    }
}

fun main() {
    val image: Image = ProxyImage("HiRes_10MB_Photo1")
    image.displayImage()
}

这是各类百科中最常见的例子,可以很好的说明代理这个设计模式,从代码中可以得知代理的特性:更强调依赖接口,接口用于规范功能约束。

接下来我们再看看委托设计模式。

委托设计模式(Delegate)将一个对象的工作委托给另一个对象。

例子:

class RealImage(private val filename: String) {
    fun displayImage() {
        println("Displaying $filename")
    }
}
​
internal class DelegateImage(private val filename: String) {
    // the "delegator"
    var p = RealImage(filename) // create the delegate
​
    fun display() {
        p.displayImage() // delegation
    }
}
​
fun main() {
    val image = DelegateImage("HiRes_10MB_Photo1")
    image.display()
}

至此我们不难发现两者的区别,对比之下,委托更为广义,代理是一种狭义上的委托,并不直接等价与委托。同时我们可以发现,委托实际上和另一种设计模式很像:装饰设计模式。

装饰设计模式(Decorator):装饰模式可以在运行时扩充一个类的功能。修饰模式是类继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。翻译成白话就是:注重对对象功能的扩展,它不关心外界如何调用,只注重对对象功能的加强,装饰后还是对象本身。

我们可以得知装饰模式主要强调:对对象增强功能。所以当我们委托时交由多个对象去处理时,则符合了装饰设计模式的概念。

经过上面的对比,我们可以得知”委托“更像是一个基本技巧,在23种常用设计模式之中许多设计模式都基于委托,如状态模式策略模式访问者模式,这些设计模式在更特殊的场合采用了委托模式。

委托的类型

上面讲完了委托,再来讲讲 Kotlin 中的委托。谷歌说:Kotlin 不仅支持类和属性的代理,其自身还包含了一些内建代理,从而使得实现委托变得更加容易。

这里重复一下委托的概念:对象会委托一个助手 (helper) 对象来处理请求,这个助手对象被称为代理。代理负责代表原始对象处理请求,并使结果可用于原始对象。

Kotlin 中的委托一共分为两种:

  • 类委托
class <类名>(b : <基础接口>) : <基础接口> by <基础对象>
  • 属性委托
val/var <属性名> : <类型> by <基础对象>

当然在网上还有第三种,实际上这类也归于属性代理

  • 局部变量委托
fun local(){
  val/var <属性名> : <类型> by <基础对象>
}

委托的应用场景

  • 类委托

假设现在有一个需求需要用到 ArrayList,并需要针对 ArrayList 进行功能扩展,增加一个 remove 后可以恢复最后一次移除的元素的方法。 通常我们会考虑继承,比如这样:

class ListWithTrash<T> : ArrayList<T>() {

   var deletedItem: T? = null

   override fun remove(element: T): Boolean {
       deletedItem = element
       return super.remove(element)
  }

   fun recover(): T? {
       return deletedItem
  }
}

但有没有考虑过还有一种特殊情况,就是进行扩展的类不支持继承,比如说被 final 修饰的。

那么就可以使用”委托“来快捷的解决:

PS:思考一下,为什么会快捷?

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
class ListWithTrash<T>(private val innerList: MutableList<T> = ArrayList<T>()) : MutableCollection<T> by innerList {
   var deletedItem: T? = null
   override fun remove(element: T): Boolean {
       deletedItem = element
       return innerList.remove(element)
  }

   fun recover(): T? {
       return deletedItem
  }
}

从这个例子也可以得知:kotlin 的委托装饰模式最方便的实现方式 – 用于实现功能代理以及功能扩展

  • 属性委托(by weak)

属性委托是较为常见的一种场景,就比如,在 Android 开发的过程中就经常会需要使用到弱引用,为什么?防内存泄露啊。

之前公司用的比较复杂,找了一个简版,好处就是:经过弱引用包装后的使用 与直接使用这个对象没什么区别。

class Weak<T : Any>(initializer: () -> T?) {
   var weakReference = WeakReference<T?>(initializer())

   constructor():this({
       null
  })

   operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
       Log.d("Weak Delegate","-----------getValue")
       return weakReference.get()
  }

   operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
       Log.d("Weak Delegate","-----------setValue")
       weakReference = WeakReference(value)
  }

}

用例:Dialog 是 Android 中最容易造成内存泄露的位置之一,这样可以有效改善因 dialog 持有生命周期对象导致生命周期对象无法回收的问题。    

private val weak by Weak { this }

   fun showDialog() {
       weak?.also {
           AlertDialog.Builder(it).show()
      }
  }

局部变量委托(by lazy)只是放在一个方法中使用,与属性委托无异。

Kotlin by关键字

看了这么多委托相关的概念,是否知道了 kotlin 的 by 关键字到底做了什么?

第一个例子,将弱引用的例子反编译成 java 代码看一下:

Weak 类:

public final class Weak {
  @NotNull
  private WeakReference weakReference;

  public Weak(@NotNull Function0 initializer) {
     Intrinsics.checkNotNullParameter(initializer, "initializer");
     super();
     this.weakReference = new WeakReference(initializer.invoke());
  }

  public Weak() {
     this((Function0)null.INSTANCE);
  }

  @NotNull
  public final WeakReference getWeakReference() {
     return this.weakReference;
  }

  public final void setWeakReference(@NotNull WeakReference var1) {
     Intrinsics.checkNotNullParameter(var1, "<set-?>");
     this.weakReference = var1;
  }

  @Nullable
  public final Object getValue(@Nullable Object thisRef, @NotNull KProperty property) {
     Intrinsics.checkNotNullParameter(property, "property");
     Log.d("Weak Delegate", "-----------getValue");
     return this.weakReference.get();
  }

  public final void setValue(@Nullable Object thisRef, @NotNull KProperty property, @Nullable Object value) {
     Intrinsics.checkNotNullParameter(property, "property");
     Log.d("Weak Delegate", "-----------setValue");
     this.weakReference = new WeakReference(value);
  }


}

用例:

   //private val weak by Weak { this }
  private final Weak weak$delegate = new Weak((Function0)(new Function0() {
     // $FF: synthetic method
     // $FF: bridge method
     public Object invoke() {
        return this.invoke();
    }

     @Nullable
     public final MainActivity invoke() {
        return MainActivity.this;
    }
  }));
 
  private final MainActivity getWeak() {
     return (MainActivity)this.weak$delegate.getValue(this, $$delegatedProperties[0]);
  }

  public final void showDialog() {
     MainActivity var10000 = this.getWeak();
     if (var10000 != null) {
        MainActivity var1 = var10000;
        boolean var2 = false;
        boolean var3 = false;
        int var5 = false;
        (new Builder((Context)var1)).show();
    }

  }

第二个例子,将 by lazy 的源码看看:

会发现实际上就是将一个闭包保存了下来并加了同步锁,在 get 时再执行 invoke 进行初始化。

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
   private var initializer: (() -> T)? = initializer
   @Volatile private var _value: Any? = UNINITIALIZED_VALUE
   // final field is required to enable safe publication of constructed instance
   private val lock = lock ?: this

   override val value: T
       get() {
           val _v1 = _value
           if (_v1 !== UNINITIALIZED_VALUE) {
               @Suppress("UNCHECKED_CAST")
               return _v1 as T
          }

           return synchronized(lock) {
               val _v2 = _value
               if (_v2 !== UNINITIALIZED_VALUE) {
                   @Suppress("UNCHECKED_CAST") (_v2 as T)
              } else {
                   val typedValue = initializer!!()
                   _value = typedValue
                   initializer = null
                   typedValue
              }
          }
      }

   override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

   override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

   private fun writeReplace(): Any = InitializedLazyImpl(value)
}

以上可以得知实际上 by 关键字做的事,和我们自己写委托做的事是一样的,本质就是 将一项工作交给代理对象去处理,代理对象经过包装加工后将需要的结果返回

所以这里也可以得知 Kotlin 委托的作用就是:”懒“,在项目中灵活使用 by 关键字可节省大量的模板代码,同时易于实现装饰模式

拓展知识

目前的 Android 开发中经常会用到 ViewBinding 和 DataBinding,为了避免内存泄露,在销毁时应该将 binding 置空,一般如果在 Base 类中进行了处理则不用考虑这些,但有些不在 Base 范畴中的情况怎么处理呢?这里有一个相对优雅的解决方案:

ViewBinding 与 Kotlin 委托双剑合璧

参考:

Wiki – 委托模式Wiki – 代理模式

Google Kotlin Vocabulary | Kotlin 委托代理

从原理分析Kotlin的延迟初始化: lateinit var和by lazy

Kotlin | 委托机制 & 原理 & 应用

利用kotlin委托属性,优雅地使用弱引用


本站由以下主机服务商提供服务支持:

0条评论

发表评论