Kotlin语法(十六)-代理(Delegation)

类代理(Class Delegation)

         代理模式提供一种实现继承的替代方式,Kotlin原生就支持代理模块。

         如下,“Derived”继承“Base”接口,并代理了它的全部公共方法:

 

[java] view plain copy

  1. interface Base {  
  2.   fun print()  
  3. }  
  4.   
  5. class BaseImpl(val x: Int) : Base {  
  6.   override fun print() { print(x) }  
  7. }  
  8.   
  9. class Derived(b: Base) : Base by b  
  10.   
  11. fun main(args: Array<String>) {  
  12.   val b = BaseImpl(10)  
  13.   Derived(b).print()  // prints 10  
  14. }  

 

         

         通过“by”关键字,将“b”实例存储到Derived对象中,编译器会生成“Base”接口的所有方法,使用“b”的实现。

 

     代理属性(Delegated Properties)

         对于很多公用属性,尽管在每次需要的时候可以通过手动实现;更好的方式是一次实现多次使用,并放到一个库(library)。

         比如,有下面类型的属性:

         Ø  延迟属性(lazy properties):只有第一次访问时才会计算值。

         Ø  观察属性(observable properties):当该属性发生改变时,会通知监听者。

         Ø  map中存储属性,不是在单独的字段中。

 

         在Kotlin中,为了满足上面几种情况,提供了代理属性(delegated properties):

 

[java] view plain copy

  1. class Example {  
  2.   var p: String by Delegate()  
  3. }  

 

 

         语法:val/var <property name>: <Type> by <expression>,“by”关键字后面的表达式就是代理(delegate);属性的“get()”和“set()”对应代理的“getValue()”和“setValue()”。属性代理不要去实现任何接口,但需要提供“getValue()”和“setValue()”(val属性不需要,var需要)函数。

         如:

 

[java] view plain copy

  1. class Delegate {  
  2.   operator fun getValue(thisRef: Any?, property: KProperty<*>): String {  
  3.     return "$thisRef, thank you for delegating '${property.name}' to me!"  
  4.   }   
  5.   operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {  
  6.     println("$value has been assigned to '${property.name} in $thisRef.'")  
  7.   }  
  8. }  

 

 

         当读取“p”的值时,实际是访问它的代理“Delegate”实例的“getValue()”函数,第一个参数是读取“p”的实例(即对应的类的实例),第二个参数为“p”自身的描述。如:

[java] view plain copy

  1. val e = Example()  
  2. println(e.p)  //Example@33a17727, thank you for delegating ‘p’ to me!  


         同样,当给“p”赋值时,会调用代理的“setValue()”函数;前面两个参数跟“getValue()”函数一致,第三个参数为赋的值。如:

 

[java] view plain copy

  1. e.p = "NEW"  
  2. //打印结果:  
  3. //NEW has been assigned to ‘p’ in Example@33a17727.  

 

 

      属性代理的要求(Property Delegate Requirements)

         下面总结代理属性的要求:

         只读属性(read-only,使用val定义)

         代理类提供“getValue”函数,参数要求:

         Ø  接收者(receiver):第一个参数,必须是属性对应的类或父类型。

         Ø  元数据(metadata):第二个参数,必须是“KProperty<*>”或它的父类型。

         Ø  该函数,必须返回一个跟属性同类型的值。

 

         可变属性(mutable,使用var定义)

         代理类的“getValue”函数跟只读属性的一样;另外还需要提供一个“setValue”函数,参数要求:

         Ø  接收者(receiver):第一个参数,同“getValue”对应的参数。

         Ø  元数据(metadata):第二个参数,同“getValue”对应的参数。

         Ø  新值:第三个参数,类型必须跟属性一样或其父类型。

 

         “getValue”与“setValue”函数,可以是代理类的成员函数或扩展函数。当需要将一个对象作为一个代理属性的代理,而该类没有对应的“getValue”与“setValue”函数,通过扩展函数方式实现就非常方便。

         另外两个函数需要使用“operator”关键字修饰。

 

         标准代理(Standard Delegates)

         Kotlin的标准库,通过工厂方法提供了一些有用的代理类。

         Lazy

         “lazy()”函数接受Lambda表达式 并 返回“Lazy<T>”实例,它可以当做延迟属性的代理实现:当第一次属性执行“get()”(获取属性值,即使用该属性)时,会通过“lazy()”函数执行添加的Lambda表达式并记录返回值;后续再使用该属性时,直接使用记录的值。

[java] view plain copy

  1. val lazyValue: String by lazy {  
  2.     println("computed!")  
  3.     "Hello"  
  4. }  
  5.   
  6. fun main(args: Array<String>) {  
  7.     println(lazyValue)  
  8.     println(lazyValue)  
  9. }  
  10. //prints:  
  11. //computed!  
  12. //Hello  
  13. //Hello  

 

         默认情况,延迟属性的赋值是线程同步的:只会在一个线程中计算一次值,其他线程使用同一个值。如果初始化同步不是必须的,可以通过将“lazy()”函数的参数设置为“LazyThreadSafetyMode.PUBLICATION”,那么多个线程对其同时赋值。如果可以确保属性只会在单个线程中初始化,可以将“lazy()”设置“LazyThreadSafetyMode.NONE”模式,该模式下,不会确保线程安全及相关开销。

 

         可观察方法(Observable)

         “Delegates.observable()”,包含两个参数:初始化值和 属性值修改的回调handler;每次对属性赋值操作,都会回调该handler方法(在属性赋值后执行),该方法包含三个参数,分别为:属性对象,原值,新值。

[java] view plain copy

  1. class User {  
  2.     var name: String by Delegates.observable("<nomalName>") {  
  3.         prop, old, new ->  
  4.         println("$old -> $new")  
  5.     }  
  6. }  
  7.   
  8. fun main(args: Array<String>) {  
  9.     val user = User()  
  10.     user.name = "first"  
  11.     user.name = "second"  
  12. }  
  13.   
  14. //结果:  
  15. //< nomalName > -> first  
  16. //first -> second  


         另,如果需要拦截修改属性值动作并禁止修改,可以使用“Delegates.vetoable()”,参数跟“observable()”类似,第二个回调handler需要返回一个Boolean,true表示同意修改,false表示禁止修改;该回调会在属性值修改前调用。

 

[java] view plain copy

  1. class User {  
  2.     var name: String by Delegates. vetoable ("<nomalName>") {  
  3.         prop, old, new ->  
  4.         println("want modify $old -> $new")  
  5.         false  
  6. }  
  7. }  
  8.   
  9. fun main(args: Array<String>) {  
  10. val user = User()  
  11. println(user.name)  
  12. user.name = "newValue"  
  13. println(user.name)  
  14. }  
  15.   
  16. //结果:  
  17. //< nomalName >  
  18. //want modify < nomalName > -> newValue  
  19. //< nomalName >  

 

         Storing Properties in a Map

         经常会在map中存储属性值,经常用于如解析JSON或其他“动态”事情。可以使用map对象作为一个代理属性的代理:

[java] view plain copy

  1. class User(val map: Map<String, Any?>) {  
  2.     val name: String by map  
  3.     val age: Int     by map  
  4. }  
  5.   
  6. //  
  7. val user = User(mapOf(  
  8.     "name" to "John Doe",  
  9.     "age"  to 25  
  10. ))  
  11.   
  12. //  
  13. println(user.name) // Prints "John Doe"  
  14. println(user.age)  // Prints 25  

         注:若属性定义为“var”,需要使用“MutableMap ”代替只读的map。
 

转载于:https://my.oschina.net/u/1177694/blog/908344

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值