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

当然,委托在软件行业不是什么新鲜名词。委托 (Delegation) 是一种设计模式,在该模式中,对象会委托一个助手 (helper) 对象来处理请求,这个助手对象被称为代理。代理负责代表原始对象处理请求,并使结果可用于原始对象。

  • 委托 (Delegation)

    https://en.wikipedia.org/wiki/Delegation_pattern

Kotlin 不仅支持类和属性的代理,其自身还包含了一些内建代理,从而使得实现委托变得更加容易。

类代理

这里举个例子,您需要实现一个同 ArrayList 基本相同的用例,唯一的不同是此用例可以恢复最后一次移除的项目。基本上,实现此用例您所需要的就是一个同样功能的 ArrayList,以及对最后移除项目的引用。

实现这个用例的一种方式,是继承 ArrayList 类。由于新的类继承了具体的 ArrayList 类而不是实现 MutableList 接口,因此它与 ArrayList 的实现高度耦合。

如果只需要覆盖 remove() 函数来保持对已删除项目的引用,并将 MutableList 的其余空实现委托给其他对象,那该有多好啊。为了实现这一目标,Kotlin 提供了一种将大部分工作委托给一个内部 ArrayList 实例并且可以自定义其行为的方式,并为此引入了一个新的关键字: by。

让我们看看类代理的工作原理。当您使用 by 关键字时,Kotlin 会自动生成使用 innerList 实例作为代理的代码:

<!-- 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}
}

by 关键字告诉 Kotlin 将 MutableList 接口的功能委托给一个名为 innerList 的内部 ArrayList。通过桥接到内部 ArrayList 对象方法的方式,ListWithTrash 仍然支持 MutableList 接口中的所有函数。与此同时,现在您可以添加自己的行为了。

工作原理

让我们看看这一切是如何工作的。如果您去查看 ListWithTrash 字节码所反编译出的 Java 代码,您会发现 Kotlin 编译器其实创建了一些包装函数,并用它们调用内部 ArrayList 对象的相应函数:

public final class ListWithTrash implements Collection, KMutableCollection {@Nullableprivate Object deletedItem;private final List innerList;@Nullablepublic final Object getDeletedItem() {return this.deletedItem;}public final void setDeletedItem(@Nullable Object var1) {this.deletedItem = var1;}public boolean remove(Object element) {this.deletedItem = element;return this.innerList.remove(element);}@Nullablepublic final Object recover() {return this.deletedItem;}public ListWithTrash() {this((List)null, 1, (DefaultConstructorMarker)null);}public int getSize() {return this.innerList.size();}// $FF: 桥接方法public final int size() {return this.getSize();}//…...
}

注意: 为了在生成的代码中支持类代理,Kotlin 编译器使用了另一种设计模式——装饰者模式。在装饰者模式中,装饰者类与被装饰类使用同一接口。装饰者会持有一个目标类的内部引用,并且包装 (或者装饰) 接口提供的所有公共方法。

  • 装饰者模式

    https://en.wikipedia.org/wiki/Decorator_pattern

在您无法继承特定类型时,委托模式就显得十分有用。通过使用类代理,您的类可以不继承于任何类。相反,它会与其内部的源类型对象共享相同的接口,并对该对象进行装饰。这意味着您可以轻松切换实现而不会破坏公共 API。

属性代理

除了类代理,您还可以使用 by 关键字进行属性代理。通过使用属性代理,代理会负责处理对应属性 get 与 set 函数的调用。这一特性在您需要在其他对象间复用 getter/setter 逻辑时十分有用,同时也能让您可以轻松地对简单支持字段的功能进行扩展。

让我们假设您有一个 Person 类型,定义如下:

class Person(var name: String, var lastname: String)

该类型的 name 属性有一些格式化需求。当 name 被赋值时,您想要确保将第一个字母大写的同时将其余字母格式化为小写。另外,在更新 name 的值时,您想要自动增加 updateCount 属性。

您可以像下面这样实现这一功能:

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, var lastname: String) {var name: String = nameset(value) {name = value.toLowerCase().capitalize()updateCount++}var updateCount = 0
}

上述代码当然是可以解决问题的,但若需求发生改变,比如您想要在 lastname 的值发生改变时也增加 updateCount 的话会怎样?您可以复制粘贴这段逻辑并实现一个自定义 setter,但这样一来,您会发现自己为所有属性编写了完全相同的 setter。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, lastname: String) {var name: String = nameset(value) {name = value.toLowerCase().capitalize()updateCount++}var lastname: String = lastnameset(value) {lastname = value.toLowerCase().capitalize()updateCount++}var updateCount = 0
}

两个 setter 方法几乎完全相同,这意味着这里的代码可以进行优化。通过使用属性代理,我们可以将 getter 和 setter 委托给属性,从而可以复用代码。

与类代理相同,您可以使用 by 来代理一个属性,Kotlin 会在您使用属性语法时生成代码来使用代理。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, lastname: String) {var name: String by FormatDelegate()var lastname: String by FormatDelegate()var updateCount = 0
}

像这样修改以后,name 和 lastname 属性就被委托给了 FormatDelegate 类。现在让我们来看看 FormatDelegate 的代码。如果您只需要委托 getter,那么代理类需要实现 ReadProperty<Any?, String>;而如果 getter 与 setter 都要委托,则代理类需要实现 ReadWriteProperty<Any?, String>。在我们的例子中,FormatDelegate 需要实现 ReadWriteProperty<Any?, String>,因为您想在调用 setter 时执行格式化操作。

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->class FormatDelegate : ReadWriteProperty<Any?, String> {private var formattedString: String = ""override fun getValue(thisRef: Any?,property: KProperty<*>): String {return formattedString}override fun setValue(thisRef: Any?,property: KProperty<*>,value: String) {formattedString = value.toLowerCase().capitalize()}
}

您可能已经注意到,getter 和 setter 函数中有两个额外参数。第一个参数是 thisRef,代表了包含该属性的对象。thisRef 可用于访问对象本身,以用于检查其他属性或调用其他类函数一类的目的。第二个参数是 KProperty<*>,可用于访问被代理的属性上的元数据。

回头看一看需求,让我们使用 thisRef 来访问和增加 updateCount 属性:

<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->override fun setValue(thisRef: Any?,property: KProperty<*>,value: String
) {if (thisRef is Person) {thisRef.updateCount++}formattedString = value.toLowerCase().capitalize()
}

工作原理

为了理解其工作原理,让我们来看看反编译出的 Java 代码。Kotlin 编译器会为 name 和 lastname 属性生成持有 FormatDelegate 对象私有引用的代码,以及包含您所添加逻辑的 getter 和 setter。

编译器还会创建一个 KProperty[] 用于存放被代理的属性。如果您查看了为 name 属性所生成的 getter 和 setter,就会发现它的实例存储在了索引为 0 的位置, 同时 lastname 被存储在索引为 1 的位置。

public final class Person {// $FF: 合成字段static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "lastname", "getlastname()Ljava/lang/String;"))};@NotNullprivate final FormatDelegate name$delegate;@NotNullprivate final FormatDelegate lastname$delegate;private int updateCount;@NotNullpublic final String getName() {return this.name$delegate.getValue(this, $$delegatedProperties[0]);}public final void setName(@NotNull String var1) {Intrinsics.checkParameterIsNotNull(var1, "<set-?>");this.name$delegate.setValue(this, $$delegatedProperties[0], var1);}//...
}

通过这一技巧,任何调用者都可以通过常规的属性语法访问代理属性。

person.lastname = “Smith”
// 调用生成的 setter,增加数量println(“Update count is $person.count”)

Kotlin 不仅支持委托模式的实现,同时还在标准库中提供了内建的代理,我们将在另一篇文章中进行详细地介绍。

代理可以帮您将任务委托给其他对象,并提供更好的代码复用性。Kotlin 编译器会创建代码以使您可以无缝使用代理。Kotlin 使用简单的 by 关键字语法来代理属性或类。内部实现上,Kotlin 编译器会生成支持代理所需的所有代码,而不会暴露任何公共 API 的修改。简而言之,Kotlin 会生成和维护所有代理所需的样板代码,换句话说,您可以将您的工作放心地委托给 Kotlin。

一本手册尽览 Android 11 最新特性与开发技巧

更有成功心得助您举一反三

☟ 即刻下载 ☟


推荐阅读

 点击屏末  | 了解更多关于用 Kotlin 进行 Android 开发的相关资料


Kotlin Vocabulary | Kotlin 委托代理相关推荐

  1. 【Kotlin】Kotlin 教程

    kotlin 教程 前言 什么是kotlin 什么是Java? kotlin 与 Java有什么区别 kotlin 的特点 Java的特点 kotlin 的历史 JAVA的历史 kotlin 的优势 ...

  2. 《From Java To Kotlin》-Kotlin与Java的简单对比

    前言: 原文来自https://github.com/MindorksOpenSource/from-java-to-kotlin 本文也只是简单比较kotlin与java的不同之处,实际学习kotl ...

  3. 【Kotlin】Kotlin 自定义组件 ( 自定义 View | 自定义 SurfaceView )

    文章目录 一.自定义 View 组件 ( Kotlin ) 二.自定义 SurfaceView 组件 ( Kotlin ) 自定义组件构造函数统一在 constructor(context: Cont ...

  4. 【Kotlin】Kotlin 中使用 ButterKnife ( 仅用于适配 Kotlin 语言 | 不推荐新项目使用 )

    文章目录 I . 特别注意 : ButterKnife 已停止维护 ( 新项目禁止使用该框架 ) II . Android Studio 中配置 Kotlin 和 ButterKnife 步骤 III ...

  5. 【Kotlin】Kotlin 构造函数 ( 主构造函数 | 主构造函数声明属性 | init 初始化代码块 | 次构造函数 | 构造函数委托 | 调用构造函数创建实例对象 )

    文章目录 I . 主构造函数 II . 主构造函数声明属性 III . init 初始化代码块 IV . 主构造函数参数 和 成员变量访问方式 V . 主构造函数 可见性 设置 VI . 次构造函数 ...

  6. 【Kotlin】Kotlin 领域特定语言 DSL 原理 二 ( 中缀表达式 )

    文章目录 I . 中缀表达式 ( 中缀调用 ) II . 中缀表达式 ( 中缀调用 ) 示例代码 I . 中缀表达式 ( 中缀调用 ) 1 . 中缀表达式作用 : 中缀表达式是 Kotlin 领域特定 ...

  7. 【Kotlin】Kotlin 领域特定语言 DSL 原理 一 ( DSL 简介 | 函数 / 属性扩展 )

    文章目录 I . DSL 简介 II . Kotlin 函数扩展 III . Kotlin 属性扩展 I . DSL 简介 1 . DSL ( Domain Specific Language ) 领 ...

  8. 【Kotlin】Kotlin 语言集合中的高阶函数详解 ( 数据类 data class | maxBy | minBy | filter | map | any | count | find )

    文章目录 I . List 集合高阶函数引入 II . Kotlin 数据类 ( data class ) III . Java 代码 与 Kotlin 代码实现对比 ( 查询年龄最大的 ) IV . ...

  9. 【Kotlin】Kotlin enum 枚举类 ( 常用用法 | 初始化成员变量 | 实现抽象方法 | 实现接口 | 获取名称和位置索引 | 调用枚举常量方法 )

    文章目录 I . 枚举类常用用法 II . 枚举类初始化 III. 枚举类匿名类 IV . 枚举类实现接口 V . 获取枚举常量名称和位置索引 VI . 获取枚举常量的成员变量 VII . 调用枚举常 ...

最新文章

  1. 2017android开发找不到工作,揭秘蒋多多2017生活现状,30岁找不到工作曾绝望自杀...
  2. 二叉排序树(c/c++)
  3. MRCTF2020]你传你码呢
  4. python opencv 界面按钮_PyAutoGUI:自动化键鼠操作的Python类库
  5. 如何编写代码使jsp程序错误在输入框上面显示_252尚学堂百战程序员049天
  6. 问答项目---登陆也要做验证!(JS和PHP验证)
  7. [Qt教程] 第24篇 数据库(四)SQL查询模型QSqlQueryModel
  8. C#学生信息管理系统
  9. Dapr牵手.NET学习笔记:Actor一个场景
  10. 在Python中使用Twitter Rest API批量搜索和下载推文
  11. 【loj3056】【hnoi2019】多边形
  12. paip.java 调用c++ dll so总结
  13. docker镜像制作、数据管理
  14. 蓝牙耳机播放音频无声问题的解决方案
  15. 怎么 把计算机里的照片变成背景,用画图怎么改照片底色
  16. Mac共享主机网络给虚拟机
  17. 环保性能高的家装乳胶漆怎么选择
  18. 花式吊打源码中 Android.mk 集锦
  19. git使用大全,强大的项目管理工具
  20. AI绘画工具软件网站合集:这些人工智能绘画生成器效果太赞了

热门文章

  1. Redis-主从、哨兵、集群
  2. 病毒防火墙推荐 - avast! antivirus Home Edition 5
  3. openframeworks播放mp4,avi的视频格式
  4. Vue混入mixins,让你减少一半代码
  5. 微软活动目录墓碑时间
  6. 怎样用CMD命令强行删除目录
  7. android malware
  8. 魅族手机显示无服务器,魅族手机被flyme锁定怎么办?魅族与魅蓝手机已锁定解决办法...
  9. 微信公众号增粉秘籍:三大矩阵简介
  10. php7.4.30 | php8.1.12 源码安装zip扩展