玖叶教程网

前端编程开发入门

简单聊聊Kotlin的委派(一)

有种完成工作的方式就是把工作委派给其他人。当然我并非讨论的是,你将你的工作委派给你的朋友,而是将你的工作从一个对象委派到另外一个对象,质量有守恒定律,同样工作必须有人去完成。

你猜怎么着,委派在软件行业并不是一个新兴的模式。委派是23种设计模式中的一种,具体对象通过委派给一个助理对象来处理相关的请求。委派对象代表原始对象来相应请求的处理,并使得处理的结果被原始对象所使用。

Kotlin通过提供对类和属性的委派使得使用kotlin来创建委派时更容易、更简单和方便。甚至Kotlin还包含一些自己的内置委托。


类的委派

现在有个需求,一个 ArrayList 可以恢复它最后一个被删除的元素。基本上,所有你需要的就是和 ArrayList 一样的功能,同时还需要一个最后一个被移除元素的引用。

其中的一个做法就是继承 ArrayList。因为新的类是继承自具体的ArrayList,而不是实现了 MutableList 接口,因此新类的实现方式和 ArrayList 存在高度的耦合。

如果你可以直接覆盖掉 ArrayListremove 方法,来新增一个被删除元素的引用,并委派 MutableList 的大部分空实现给其它的对象,这种方式岂不是更好么?Kotlin提供了一种方式来实现刚才的的思路,它就是通过委派大多数工作给一个内部的 ArrayList 实例来实现的,并且可以自定义它自己的行为。为了实现这个功能,Kotlin引入了一个新的关键字 : by .

我们来看看类委派是如何实现的。当使用 by 关键字时,Kotlin会自动生成使用 innerList 实例的代码来作为委派:

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 实例代理。ListWithTrash 依然会通过内部 ArrayList 对象直接桥接方法的方式支持 MutableList 接口的所有功能。另外你还可以添加自己自定义的能力。

源码之下:

我们来看看这个是如何实现的。如果我们来查看 ListWithTrash 反编译之后的 Java 代码,你可以发现其实 Kotlin 为 ArrayList 调用的相关方法都创建了包装方法:

public final class ListWithTrash implements Collection, KMutableCollection {
  
  @Nullable
  private Object deletedItem;
  private final List innerList;

  @Nullable
  public 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);
  }

  @Nullable
  public final Object recover() {
     return this.deletedItem;
  }

  public ListWithTrash() {
     this((List)null, 1, (DefaultConstructorMarker)null);
  }

  public int getSize() {
     return this.innerList.size();
  }

  // $FF: bridge method
  public final int size() {
     return this.getSize();
  }
  //...and so on
}

提示:在自动生成的代码中,Kotlin 编辑器其实使用了另外一种称之为装饰模式的设计模式来支持了 Kotlin 的委派功能。在装饰模式中,装饰类和被装饰类共享了同一套接口。装饰类保留目标类的引用,并包装或者装饰该接口提供的所有的共有方法。


当你不能继承特定的类对象时,委派将会非常的有用。通过类委派,虽然你的类对象不是任何类的继承关系的一部分。但是它共享了相同的接口,并装饰了原始类型的内部对象。这就意味着你在不破坏公有的 API 的情况下,很容易去改变相关的实现。

委派属性

除了类的委派外,你也可以使用 by 关键字来委派属性。通过属性委派来负责对属性的get和set方法进行调用。当你需要在不同的对象上重用 getter/setter 方法时,这可能非常的有用。通过简单的后备字段就可以很容易地扩展字段。

来想想你有一个 Person 类定义如下:

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

name 属性需要一些格式化的需求。name 设置如下:你想首字母大写,其余剩下的字母小写。当然,当更新 name 时,你想自动地增加更新属性次数的记录。

那么你可能会这么去写:

class Person(name: String, var lastname: String) {
   var name: String = name
       set(value) {
           name = value.toLowerCase().capitalize()
           updateCount++
       }
   var updateCount = 0
}


当然了 这很有效果。但是如果当你的需求改变了,当你的 lastname 改变时也每次增加 updataCount 的次数。你可能会 复制 name set的逻辑,但是你会发现你为每个属性都写了同样一套setter方法:

class Person(name: String, lastname: String) {
  
   var updateCount = 0
  
   var name: String = name
       set(value) {
           name = value.toLowerCase().capitalize()
           updateCount++
       }
  
   var lastname: String = lastname
       set(value) {
           lastname = value.toLowerCase().capitalize()
           updateCount++
       }
}

两个 setter 方法基本上都相同,我告诉你其中一个没有必要存在。通过属性的委托,你可以通过委托 gettersetter 方法来实现代码的复用。

就像 类委托一样,你可以使用 by 关键字来委托一个属性。而且 Kotlin 会自动生成代码来委托你的属性。

class Person(name: String, lastname: String) {
   var name: String by FormatDelegate()
   var lastname: String by FormatDelegate()
   var updateCount = 0
}

通过这种更改,你已经委托了 namelastname 属性给 FormatDelegate 类了。现在我们来看一看 FormatDelegate 类长什么样。当你只需要委托 getter 方法时,这个委托类需要实现 ReadProperty<Any?,String> 接口,而当你需要委托 getter/setter 两个方法时,你需要实现 ReadWriteProperty<Any?,String> 接口了。在我们的当前情况下,FormatDeletegate 需要实现 ReadWriteProperty<Any?,String> 接口,因为你想当 setter方法被调用时,进行格式化。

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()
   }
}

你可能会注意到 setter 和 getter 方法上面有两个额外的参数. 第一个参数 thisRef 表示拥有这个属性的对象。thisRef 可以被用来访问对象本身,例如检查其他属性或者调用其他函数等等。第二个参数 KProperty<*> 可以被用来接近委托属性的元数据(这个以后咱们再讲).

回看我们的需求,我们可以使用 thisRef 来获取我们的 updateCount 属性并增加它。

override fun setValue(
   thisRef: Any?,
   property: KProperty<*>,
   value: String
) {
  
   if (thisRef is Person) {
       thisRef.updateCount++
   }
  
   formattedString = value.toLowerCase().capitalize()
}

源码之下:

为了理解委派属性是如何工作的,我们来看一看反编译之后的 Java 代码。Kotlin 编译器为 name 和 lastname 都生成了 私有的 FormatDelegate 引用,伴随着 setter/getter方法,你的添加的逻辑也被加入其中。

编译器同时也生成了 KProperty[] 在存储 委派的属性。如果你看看 name 属性自动生成的 gettter 和 setter 方法,name对应的 FormateDelegate 实例存储在索引0的位置,而 lastname 对应的实例存储在索引1位置。

public final class Person {
  // $FF: synthetic field
  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;"))};
  @NotNull
  private final FormatDelegate name$delegate;
  @NotNull
  private final FormatDelegate lastname$delegate;
  private int updateCount;

  @NotNull
  public 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 方法,增加 count 计数。

在Kotlin标准库中,Kotlin不仅支持委托,而且还内置了委托,将来我们会聊聊这方面的东西。


总结:

委托可以帮助你委托任务到其它对象,并且提供了更好的代码复用。Kotlin编译器生成的代码让你无缝使用委托。Kotlin通过 by关键字简单语法来委托一个属性或者一个类。在反编译源码之下,Kotlin编译器自动生成了所有必要的代码来支持委托,并没有暴露对公有 API 的更改。简单来说,Kotlin生成并维护了所有需要的样板代码来供我们使用委托,或者换句话说,你可以在Kotlin中委派自己的委派任务。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言