委托的概念
有时候,完成一些工作的方法是将它们委托给别人。这里不是在建议您将自己的工作委托给朋友去做,而是在说将一个对象的工作委托给另一个对象。
委托 (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 范畴中的情况怎么处理呢?这里有一个相对优雅的解决方案:
参考:
Google Kotlin Vocabulary | Kotlin 委托代理
本站由以下主机服务商提供服务支持:
0条评论