Kotlin 学习之 by Delegation

转载自:https://www.jianshu.com/p/54a57aac11e6

代理模式是23种经典设计模式之一,代理模式被认为是继承的更好替代解决方案,因为代理比继承更加灵活,在Java的语言中,通过反射可以实现动态代理,动态代理可以实现AOP编程,即:可以动态地往已有类中添加逻辑;比如:实现事务的自动提交,异常的自动捕获,热修复等等;

在科特林语言中,代理模式是默认支持的,不需要任何额外的代码,你只需要记住一个关键字通过我们不妨来试一下:

interface Base {
    fun sayHi()
}

class BaseImpl : Base {
    override fun sayHi() {
        println("BaseImpl->sayHi")
    }
}
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl()
    val derived = Derived(b)
    derived.sayHi()
}

这里派生作为BaseImpl的代理类,拥有BaseImpl类中的所有方法,派生将代理BaseImpl类执行BaseImpl类中的所有方法,就像继承自BaseImpl类一样。这样说起来有点抽象,来看一下科特林编译器具体为我们做了一些什么。但是,怎么看呢?教大家一个方法!
大家都知道,科特林和Java的均是JVM语言,最终均转换到同样的的Java字节码,这样我们就可以先将科特林编译为的.class文件,再反编译为的.java文件,看看对应的Java的代码,我们就可以看到更多的细节下面是最终反编译生成的Java的代码:

public final class Derived implements Base {
  public Derived(@NotNull Base b) {
    this.$$delegate_0 = b;
  }
  public void sayHi() {
    this.$$delegate_0.sayHi();
  }
}

这里,我们可以清楚地看到,科特林编译器为我们动态添加了一个成员变量$$ delegate_0,这个成员变量代表被代理的对象,这里对应的是BaseImpl对象,导出里面的sayHi的方法最终调用是代理对象的sayHi的方法,即科特林编译器帮我们提供了一个非常漂亮的代理模式实现。

代理属性

在一些情况下,我们可能希望某些属性延迟加载,即在我们正在需要的时候才对它赋值;亦或者我们希望可以随时监听属性值的变化;在上述这些场景中,代理属性就可以发挥作用了。

代理属性的语法格式如下:

class DelegateProperty {
    val d: String by Delegate()
}
class Delegate {
    operator fun getValue(thisRef: Any? , property: KProperty<*>): String {
        return "Invoke getValue() , thisRef = $thisRef , property name = ${property.name}"
    }
    operator fun setValue(thisRef: Any? , property: KProperty<*> , value: String) {
        println("Invoke setValue() , thisRef = $thisRef , property name = ${property.name} , value = $value")
    }
}

fun main(args: Array<String>) {
    val dp = DelegatedProperty()
    dp.d = "Value0" // Invoke setValue() , thisRef = DelegatedProperty@2ef1e4fa , property name = d , value = Value0
   
    println(dp.d) // Invoke getValue() , thisRef = DelegatedProperty@2ef1e4fa , property name = d
}

这里的代理是如何实现的呢我们知道,科特林的属性值会自动生成的set / get方法,而代理类通过代理设置/获取方法生成相应的代理方法,这里的方法对应关系如下?

// thisRef对应代理对象的引用,property对应代理属性的反射属性封装
// 注意这里的代理方法一定要添加operator关键字,operator关键字是重载操作符关键字,后续的文章中会讲到,敬请期待
get() -> operator fun getValue(thisRef: Any? , property: KProperty<*>)
set() -> operator fun setValue(thisRef: Any? , property: KProperty<*> , value: T)

科特林标准库提供了一些常用代理的方法实现,即上文提到的几种代理,先来看第一种:延迟加载。

延迟加载

科特林提供了一个懒惰的方法用于实现延迟加载,懒方法有一个拉姆达表达式参数,用于对属性进行初始化赋值,而一旦完成赋值,该拉姆达表达式将不会再次调用的λ表达式调用发生在。第一次使用该属性的时候,即实现了属性赋值的延迟加载来看一个简单的例子:

// 使用标准库实现的lazy函数,实现属性的延迟加载
private val lazyValue: String by lazy {
    println("调用该初始赋值表达式完成赋值")
    // 这里是实际赋值
    "Hello, world"
}
fun main(args: Array<String>) {
    // 仅在第一次会调用lazy方法的lambda表达式
    println(lazyValue) // 打印:调用该初始赋值表达式完成赋值
    println(lazyValue) // 打印: Hello, world, 再次调用将不再调用lambda表达式
}

懒惰的方法是一个线程安全的延迟加载方法,为了加深大家的理解,根据上面的原理,我们尝试自己来实现一个非线程安全的延迟加载方法,看具体实现:

private object UNINITIALIZE_VALUE

class MyLazy<T>(initialize: ()->T) {
    private var value: Any? = UNINITIALIZE_VALUE
    private val initialize = initialize
    operator fun getValue(thisRef: Any? , property: KProperty<*>): T {
        if(value == UNINITIALIZE_VALUE) {
            value = initialize()
        }
        return value as T
    }
    operator fun setValue(thisRef: Any? , property: KProperty<*> , value: T) {
        this.value = value
    }
}
// 为了和标准库区分,使用__lazy命名
fun <T>  __lazy(initialize: () -> T): MyLazy<T> = MyLazy(initialize)

var lazyValue1 by __lazy {
    println("自定义lazy初始化赋值表达式被调用")
    "Hello , world"
}

fun main(args: Array<String>) {
    // 自定义延迟加载函数__lazy
    println(lazyValue1)
    lazyValue1 = "Other value"
    println(lazyValue1)
}

由此可见,实现一个延迟加载接口并不复杂,最重要的是要理解延迟加载的过程以及实现原理总结实现延迟加载接口,需要注意三个地方:

  • 需要提供初始化拉姆达表达式参数,用于初始赋值
  • 需要实现代理属性对象的的setValue /的getValue方法,如果是VAL则只需要实现的getValue即可
  • 需要严格确保属性不会被多次初始化

观察的属性

科特林标准库还提供了一个可观察属性,这个属性使用观察者模式实现,如果属性值发生变化则会调用相应的回调拉姆达接口通知使用者,先看一个具体的例子:

var observableValue by Delegates.observable("Initial value") {  prop , old , new ->
    println("$old -> $new")
}

fun main(args: Array<String>) {
    println(observableValue)    // 打印:Initial value
    observableValue = "Hello"   // 打印: Initial value -> Hello
    println(observableValue)    // 打印:Hello
}

这里的具体实现,感兴趣的同学请参看文章开头的方法进行追踪!

在地图中存储属性

。这也是科特林标准库提供的一个非常有用的特性,它主要用于JSON数据的解析看官方的例子:

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

val user = User(mapOf(
    "name" to "John Doe",
    "age" to 25
))

该方法比较简单,这里就不再赘述了!

总结

至此,关于代理的介绍可以暂时告一段落了!
代理模式是一个非常经典设计模式,在解决某些问题中可以发挥事半功倍的效果。幸运的是,科特林语言原生支持代理模式,实现代理模式如同声明一个属性一样简单。而且,代理模式的设计也非常漂亮,仅仅使用一个关键字通过极尽简约之美。在日常编码中,一定要灵活运用代理模式,比如实现延迟加载,实现属性观察等等。KotterKnife是一个非常经典的代理模式的实现例子,有兴趣的同学可以克隆该仓库,查看源码,领会代理模式的优美。

欢迎加入科特林交流群

如果你也喜欢Kotlin语言,欢迎加入我的Kotlin交流群:329673958,一起来参与Kotlin语言的推广工作。

文章源码地址

Kotliner:** https://github.com/yuanhoujun/Kotliner **别忘了点击仓库右上方的明星哦!




猜你喜欢

转载自blog.csdn.net/jiankeufo/article/details/80887178