注册

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 还支持自定义属性委托。自定义属性委托需要实现 getValuesetValue 方法,用于获取和设置属性的值,与 Java 的委托相比,Kotlin 的属性委托更加方便和简洁,减少样板代码。


6、感谢



  1. 校稿:ChatGpt
  2. 文笔优化:ChatGpt

参考:Kotlin官方文档:委托 Kotlin官方文档:属性委托


作者:Otaku_尻男
链接:https://juejin.cn/post/7223258679259873317
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册