step by step Kotlin实现ButterKnife


欲善其事,必利其器。想要花式的使用一门语言,惊呆小伙伴,必须从实践入手。知易行难,本文会带领大家一步步的实现Kotlin版本的ButterKnife,深入的体会下Kotlin的魅力。

属性

Kotlin的属性是通过getter和setter来进行访问。完整的属性声明方式为

var :  [ =  ]
    
    

但一般情况下,getter和setter是可以省略。值得注意的是,一定不要在getter和setter方法里操作自己,这会造成死循环的~ 为了避免这种情况的发生,Kotlin提供field关键字用于属性的访问器,可以直接使用field来访问自身

var NotNullSingleString : String? = null
    get(){
      return field ?: throw IllegalStateException("not initialized")
    }
    set(value) {
        if(field == null) field = value else throw IllegalStateException("already initialized")
    }

而这样设计的好处在于,getter和setter方法很方便的被定义,可以更直接的对属性进行包装。当然,OO思想深入人心的今天,不抽象都不好意思说学过面向对象。欣慰的是,对此Kotlin提供一套更完善,优雅的解决方案——代理属性

代理属性

val view : TextView by Bind(R.id.text)

class Bind(val id: Int) : ReadOnlyProperty {
  override fun getValue(thisRef: Activity, property: KProperty<*>): V {
    return thisRef.findViewById(id) as V
  }
}

当然ReadOnlyProperty有一个兄弟接口ReadWriteProperty,还需要实现setValue(thisRef: R, property: KProperty<*>, value: T)。这两个接口分别对应val和var关键字。KProperty是属性的metadata。
细心的小伙伴们应该发现了上面代码问题,每次调用view的时候都需要去findViewById,囧。Kotlin提供了Lazy代理来解决这个问题。Lazy代理,提供的是一个线程安全的getter代理,在属性第一次被调用时,Lazy的Lambdas会被执行,来给属性进行初始化,在之后调用时,此属性将不再被初始化。

 val view : TextView by lazy {
    findViewById(R.id.text) as TextView
  }

提到Lambdas,这个众多语言的语法糖,被广大程序猿喜爱。下面我们就来看一下Kotlin的Lamdas和函数。

函数和Lambdas

函数在Kotlin中尤为重要,也是Kotlin的重要特色之一,我们先来尝点甜头

fun add(a : Int, b : Int) : Int {
    return a + b
}

fun add(a : Int, b : Int) = a + b

这个两个函数是完全一样的,我们可以很方便的省略掉返回值类型和括号。当然这还不够,我们可以传入一个Lambdas

fun compose( f: (B) -> C, g: (A) -> B ): (A) -> C {
    return {x -> f(g(x))}
}

val oddLength = compose({
    it % 2 === 0
},{
    it.length
})

上面的函数实现了一个函数组合的功能。f : (B) -> C 表示名字为f,只有一个类型为B的参数,返回C。当Lambdas只有一个参数时,可用it代替。当然,有些时候我们不必要传入这么多函数只需要一个,比如集合操作

var list = listOf(1, 2, 3)
list.filter { it % 2 !== 0 }.map { "${it}是一个质素" }

作为动态语言,Kotlin同样支持扩展,回到我们的ButterKnife。上面通过 by lazy的实现方式显然达不到我们想要的效果。同时Lazy持有一个internal constructor(),我们无法直接继承,那么扩展是一个很好方式

public fun  Activity.bview(id : Int) : Lazy{
  return lazy {
    this.findViewById(id) as V
  }
}

注意:在我们想给一个类添加全局扩展时,扩展方法要放在类的外部,并声明为public
我们可以进一步抽象,给Activiy扩展一个新的属性

private val Activity.viewFinder: Activity.(Int) -> View
    get() = { findViewById(it) }

public fun  Activity.bview(id : Int) : Lazy{
  return lazy {
    this.viewFinder(id) as V
  }
}
实际上,扩展是静态加载的,它并没有直接去修改类本身,也没有给类直接添加一个成员,只是能让这个类的实例通过 . 的方式访问。

Lambdas作为java8的新特性,被众多java的使用者所知晓,但对还在使用java6的android来说,只能望梅止渴。虽然有开源的解决方案,但在生产环境中使用依旧存在风险。不过openJDK也有可能带来很多新的特性,其中有可能就包含Lambdas。我们先行感受下。

空安全

通过上面的事例,我们大体上实现了ButterKnife的功能,接下来我们进一步对其进行完善。

optional

首先我们要明确一点,可空类型和普通类型是完全不同。如果一定要声明这个类型可空,直接使用?

val v: View = TextView(context)
var v2: View? = null

v.visibility = vi2?.visibility ?: View.GONE     

?:在Kotlin中被称作Elvis operator。当然你也可以使用!!来声明类型非空。同时,非空代理也是个不错的选择,甚至可以说更优秀,他在初始化之前调用都会报错。

 var view : View by Delegates.notNull()

之前,我们给父类添加了一个Lambdas属性viewFinder。但是,他的返回值View是可能为空的。在这里我们需要对其进行处理

public fun  Activity.bindOptionalView(id: Int)
    : ReadOnlyProperty = optional(id, viewFinder)

@Suppress("UNCHECKED_CAST")
private fun  optional(id: Int, finder: T.(Int) -> View?)
    = Lazy { t: T, desc ->  t.finder(id) as V? }

private val Activity.viewFinder: Activity.(Int) -> View?
  get() = { findViewById(it) }

private class Lazy(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty {
  private object EMPTY
  private var value: Any? = EMPTY

  override fun getValue(thisRef: T, property: KProperty<*>): V {
    if (value == EMPTY) {
      value = initializer(thisRef, property)
    }
    @Suppress("UNCHECKED_CAST")
    return value as V
  }
}

我们重新声明一个lazy代理来处理。并在view为空的时候什么也不做
致此我们基本完成了一个Kotlin版本的ButterKnife,Jake Wharton的Kotterknife

更多令人兴奋的东西

接口

Kotlin中接口的方法是可以实现的,如同java8中的接口默认方法。但属性是不能初始化的,必须在继承的类中对其初始化。
这给我们提供了一种新的思路去定义接口,只提供属性,不提供方法。比如KClass。
Kotlin的反射不同于java,它提供两种机制,View::class -> KClass View::class.java -> Class

Data Object

Data Classes不同于其他对象,他的声明尤其简单

data class User(var name : String, var age : Int)

通过这种方式,就可以简单的声明一个pojo。同时Kotlin在Java注解方面的兼容非常出色,你可以直接使用Library的注解,例如Gson

data class User(@SerializedName("_id") var id : String, var name : String, var age : Int)

单例

Kotlin提供companion object,伴随对象。在他内部声明的属性和方法可直接通过.的方式访问。但不同于java的静态块,他也是个对象,可以继承接口。并在反射中可以通过KClass.companionObjectInstance得到他的实例

class App : Application() {
    companion object {
        var instance: App by NotNullSingleValueVar()
    }

    override fun onCreate() {
            super.onCreate()
            instance = this
    }
}

private class NotNullSingleValueVar() : ReadWriteProperty {
    private var value: T? = null
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("${desc.name} " +
                "not initialized")
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = if (this.value == null) value
        else throw IllegalStateException("${desc.name} already initialized")
    }
}

扩展的域

我们可以直接引入某些方法,并在类中进行调用,这就省去了写Utility的麻烦

import foo.bar.goo // 导入所有名字叫 "goo" 的扩展
// 或者
import foo.bar.* // 导入foo.bar包下得所有数据

fun usage(baz: Baz) {
    baz.goo()
}

当然,Kotlin还有很多新奇好玩特性,同时通过实践,最直观的感受就是,他更像一个java的扩展版,一切都以java为基础,去延伸而不是修改,去添加而不是破坏。

Calling Java code from Kotlin

你可能感兴趣的:(step by step Kotlin实现ButterKnife)