5分钟速通Kotlin委托
1、什么是委托?
委托,又叫委托模式是一种常用的设计模式,它可以让一个对象在不改变自己原有的行为的前提下,将某些特定的行为委托给另一个对象来实现。它通过将对象之间的关系分离,可以降低系统的耦合度,提高代码的复用性和可维护性。
其中有三个角色,约束、委托对象和被委托对象。
- 约束: 一般为接口也可以是抽象类,定义了某个行为。
- 被委托对象: 负责执行具体的行为。
- 委托对象: 负责将约束中定义的行为交给被委托对象。
2、Java中的委托
先来说一说委托在Java中的应用用一个简单的例子来说明:
老板
在创业初期时因为只有一个人而需要负责产品的客户端
、UI
、服务器
。
这个时候老板
负责的这些工作就可以被抽象出来形成一个约束接口:
public interface Work {
void app();
void ui();
void service();
}
public class Boss implements Work {
@Override
public void app() {
System.out.println("Boss doing app");
}
@Override
public void ui() {
System.out.println("Boss doing ui");
}
@Override
public void service() {
System.out.println("Boss doing service");
}
}
现在老板
每天都在做这几件事:
public class Main {
public static void main(String[] args) {
Boss boss = new Boss();
boss.app();
boss.ui();
boss.service();
}
}
输出:
Boss doing app
Boss doing ui
Boss doing service
运气不错,产品赚了不少钱,老板
花钱雇了一个员工
,将这些工作委托给他处理,自己直接脱产,只需要知道结果就可以了,于是就有了:
public class Employee implements Work{
@Override
public void app() {
System.out.println("Employee doing app");
}
@Override
public void ui() {
System.out.println("Employee doing ui");
}
@Override
public void service() {
System.out.println("Employee doing service");
}
}
public class Boss implements Work{
private Employee employee;
public Boss(Employee employee) {
this.employee = employee;
}
@Override
public void app() {
employee.app();
}
@Override
public void ui() {
employee.ui();
}
@Override
public void service() {
employee.service();
}
}
public class Main {
public static void main(String[] args) {
Boss boss = new Boss(new Employee());
boss.app();
boss.ui();
boss.service();
}
}
Employee doing app
Employee doing ui
Employee doing service
这就是一个委托模式,老板
(委托对象)将 工作
(约束)委托给 员工
(被委托者)处理,老板
并不关心每项工作具体是如何实现的,员工
在完成工作后也会和老板
汇报,就算这几项工作内容发生变化也只是员工
需要处理。
3、Kotlin中的委托
那么针对上述的委托所描述例子在Kotlin中是如何实现的呢?
答案是使用关键字by,Kotlin专门推出了by来实现委托:
上述例子中的工作
和员工
都不变:
interface Work {
fun app()
fun ui()
fun service()
}
class Employee : Work {
override fun app() {
println("Employee doing app")
}
override fun ui() {
println("Employee doing ui")
}
override fun service() {
println("Employee doing service")
}
}
在老板
这个类中,我们要将工作
使用关键字by委托给员工
class Boss(private val employee: Employee) : Work by employee
就这么一行,实现了Java代码中老板
类的效果。
fun main(args: Array<String>) {
val boss = Boss(Employee())
boss.app()
boss.ui()
boss.service()
}
结果肯定是一样的。
那么by是如何实现Java中委托的效果的呢?通过反编译Kotlin字节码后我们看到:
public final class Boss implements Work {
private final Employee employee;
public Boss(@NotNull Employee employee) {
Intrinsics.checkNotNullParameter(employee, "employee");
super();
this.employee = employee;
}
public void app() {
this.employee.app();
}
public void service() {
this.employee.service();
}
public void ui() {
this.employee.ui();
}
}
其实就是Java中实现委托的代码,Kotlin将它包成一个关键字by,效率大幅提升。
4、属性委托
上述说明的委托都属于类委托,而在Kotlin当中by不仅可以实现类委托,还可以实现属性委托,属性委托为Kotlin的一大特性,将对属性的访问委托给另一个对象。使用属性委托可以让我们编写更简洁、更模块化的代码,并且能够提高代码的可重用性。
4.1 如何实现属性委托?
Kotlin官方文档中给出了定义:
使用方式:val/var <属性名>: <类型> by <表达式>
。
在 by 后面的表达式是该 委托, 属性对应的 get()
和set()
会被委托给它的 getValue()
与 setValue()
方法。 如果该属性是只读的(val)其委托只需要提供一个 getValue()
函数如果该属性是var则还需要提供 setValue()
函数。例如:
class Example {
var str: String by 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.")
}
}
fun main(args: Array<String>) {
val p = Example()
p.str = "Hello"
println(p.str)
}
因为属性str是可变的所以在Delegate类中实现了getValue和setValue两个函数,其中一共出现了三个参数分别是
- thisRef :读出
str
的对象 - property :保存了对
str
自身的描述 (例如你可以取它的名字) - value :保存将要被赋予的值
运行结果如下:
Hello has been assigned to 'str' in Example@1ddc4ec2.
Example@1ddc4ec2, thank you for delegating 'str' to me!
我们再将Example类中的代码转为Kotlin字节码反编译得到以下代码:
public final class Example {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.mutableProperty1(new MutablePropertyReference1Impl(Example.class, "str", "getStr()Ljava/lang/String;", 0))};
@NotNull
private final Delegate str$delegate = new Delegate();
@NotNull
public final String getStr() {
return this.str$delegate.getValue(this, $$delegatedProperties[0]);
}
public final void setStr(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.str$delegate.setValue(this, $$delegatedProperties[0], var1);
}
}
就是创建了一个Delegate对象,再通过调用setVaule和getValue一对方法来获取和设置值的。
4.2 标准委托
在Kotlin标准库为委托提供了几种方法
4.2.1 延迟属性 Lazy
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
首次访问属性时才进行初始化操作,lazy()
是接受一个 lambda 并返回一个 Lazy <T>
实例的函数,返回的实例可以作为实现延迟属性的委托, 该lambda表达式将在第一次访问该属性时被调用,初始化属性并返回属性值,之后的访问将直接返回初始化后的值。
简单的例子:
fun main(args: Array<String>) {
val str : String by lazy {
println("Hello str")
"lazy"
}
println(str)
println(str)
}
输出:
Hello str//只在第一次访问时执行
//后续访问只返回值
lazy
lazy
当我们使用 by lazy
委托实现延迟初始化时,Kotlin 编译器会生成一个私有的内部类,用于实现委托属性的懒加载逻辑,其内部包含一个名为 value
的属性,用于存储真正的属性值。同时,还会生成一个名为 isInitialized
的私有 Boolean 属性,用于标识属性是否已经初始化。
当我们首次访问被 lazy
修饰的属性时,如果它还未被初始化,就会调用 lazy
所接收的 lambda 表达式进行初始化,并将结果保存在 value
属性中。之后,每次访问该属性时,都会返回 value
中存储的属性值。
4.2.2 可观察属性 Observable
Delegates.observable()
接受两个参数:初始值与修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值与新值:
class User {
var name : String by Delegates.observable("no value") {
property, oldValue, newValue ->
println("property :${property.name}, old value $oldValue -> new value $newValue")
}
}
fun main() {
val user = User()
user.name = "Alex"
user.name = "Bob"
}
property :name, old value no value -> new value Alex
property :name, old value Alex -> new value Bob
如果你想截获赋值并“否决”它们,那么使用 vetoable()
取代 observable()
。 在属性被赋新值生效之前会调用传递给 vetoable
的处理程序,简单来说就是利用你设定的条件来决定设定的值是否生效,还是以上述代码为例,在User中增加一个年龄属性:
var age : Int by Delegates.vetoable(0) {
_, oldValue, newValue ->
println("old value : $oldValue, new value : $newValue")
newValue > oldValue
}
在这里我们设定了输入的年龄大于现在的年龄才生效,运行一下看看输出什么:
0
old value : 0, new value : 20
20
old value : 20, new value : 19
20
old value : 20, new value : 25
25
0
old value : 0, new value : 20
20
old value : 20, new value : 19
20
old value : 20, new value : 25
25
4.2.3 将属性储存在映射中
映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
class User(map: MutableMap<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main(args: Array<String>) {
val user = User(
mutableMapOf(
"name" to "Alex",
"age" to 18
)
)
println("name : ${user.name}, age : ${user.age}")
}
输出:
name : Alex, age : 18
5、总结
委托是一种常见的软件设计模式,旨在提高代码的复用性和可维护性,在 Java 中,委托通过定义接口和实现类来实现。实现类持有接口的实例,并将接口的方法委托给实例来实现。这种方式可以实现代码的复用和解耦,但是需要手动实现接口中的方法,比较繁琐,而在 Kotlin 中,委托通过by关键字实现委托其中还包括了属性委托一大特性,Kotlin 提供了很多内置的属性委托,比如延迟属性、映射属性等。此外,Kotlin 还支持自定义属性委托。自定义属性委托需要实现 getValue
和 setValue
方法,用于获取和设置属性的值,与 Java 的委托相比,Kotlin 的属性委托更加方便和简洁,减少样板代码。
6、感谢
- 校稿:ChatGpt
- 文笔优化:ChatGpt
链接:https://juejin.cn/post/7223258679259873317
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。