深圳热线科技

Kotlin 委托代理

2020-12-27 11:25:43 来源:

原标题:KotlinVocabulary|Kotlin委托代理

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

当然,委托在软件行业不是什么新鲜名词。委托(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实例作为代理的代码:

<!--Copyright2019GoogleLLC.SPDX-License-Identifier:Apache-2.0-->classListWithTrash<T>(privatevalinnerList:MutableList<T>=ArrayList<T>):MutableCollection<T>byinnerList{vardeletedItem:T?=nulloverridefunremove(element:T):Boolean{deletedItem=elementreturninnerList.remove(element)}funrecover:T?{returndeletedItem}}

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

工作原理

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

publicfinalclassListWithTrashimplementsCollection,KMutableCollection{@NullableprivateObjectdeletedItem;privatefinalListinnerList;

@NullablepublicfinalObjectgetDeletedItem{returnthis.deletedItem;}

publicfinalvoidsetDeletedItem(@NullableObjectvar1){this.deletedItem=var1;}

publicbooleanremove(Objectelement){this.deletedItem=element;returnthis.innerList.remove(element);}

@NullablepublicfinalObjectrecover{returnthis.deletedItem;}

publicListWithTrash{this((List)null,1,(DefaultConstructorMarker)null);}

publicintgetSize{returnthis.innerList.size;}//$FF:桥接方法publicfinalintsize{returnthis.getSize;}//…...}

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

  • 装饰者模式

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

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

属性代理

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

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

classPerson(varname:String,varlastname:String)

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

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

<!--Copyright2019GoogleLLC.SPDX-License-Identifier:Apache-2.0-->

classPerson(name:String,varlastname:String){varname:String=nameset(value){name=value.toLowerCase.capitalizeupdateCount++}varupdateCount=0}

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

<!--Copyright2019GoogleLLC.SPDX-License-Identifier:Apache-2.0-->

classPerson(name:String,lastname:String){varname:String=nameset(value){name=value.toLowerCase.capitalizeupdateCount++}varlastname:String=lastnameset(value){lastname=value.toLowerCase.capitalizeupdateCount++}varupdateCount=0}

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

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

<!--Copyright2019GoogleLLC.SPDX-License-Identifier:Apache-2.0-->

classPerson(name:String,lastname:String){varname:StringbyFormatDelegatevarlastname:StringbyFormatDelegatevarupdateCount=0}

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

<!--Copyright2019GoogleLLC.SPDX-License-Identifier:Apache-2.0-->

classFormatDelegate:ReadWriteProperty<Any?,String>{privatevarformattedString:String=""

overridefungetValue(thisRef:Any?,property:KProperty<*>):String{returnformattedString}

overridefunsetValue(thisRef:Any?,property:KProperty<*>,value:String){formattedString=value.toLowerCase.capitalize}}

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

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

<!--Copyright2019GoogleLLC.SPDX-License-Identifier:Apache-2.0-->

overridefunsetValue(thisRef:Any?,property:KProperty<*>,value:String){if(thisRefisPerson){thisRef.updateCount++}formattedString=value.toLowerCase.capitalize}

工作原理

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

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

publicfinalclassPerson{//$FF:合成字段staticfinalKProperty[]$$delegatedProperties=newKProperty[]{(KProperty)Reflection.mutableProperty1(newMutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class),"name","getNameLjava/lang/String;")),(KProperty)Reflection.mutableProperty1(newMutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class),"lastname","getlastnameLjava/lang/String;"))};@NotNullprivatefinalFormatDelegatename$delegate;@NotNullprivatefinalFormatDelegatelastname$delegate;privateintupdateCount;

@NotNullpublicfinalStringgetName{returnthis.name$delegate.getValue(this,$$delegatedProperties[0]);}

publicfinalvoidsetName(@NotNullStringvar1){Intrinsics.checkParameterIsNotNull(var1,"<set-?>");this.name$delegate.setValue(this,$$delegatedProperties[0],var1);}//...}

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

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

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

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

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

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

了解更多关于用Kotlin进行Android开发的相关资料

来源:搜狐

深圳热线移动端

深圳热线移动端

扫码访问移动深圳热线,更多精彩内容随你看。

推荐 28239
0 条评论 / 0 人参与 网友评论 跟帖管理 举报

热门推荐