一步一步掌握在Android中使用Kotlin DSL

译者前言:在阅读本篇文章之前,你需要了解Kotlin以下知识点:

  • 扩展函数(Extension Function)
  • 中缀表达式或中缀调用(Infix Function)
  • 运算符重载(Operator Overloading)

在这篇文章中,我们将要学习如何在Android项目中编写Kotlin DSL。

本文篇幅较长,需要花费一定时间来阅读,从而来一起编写DSL。我们将会阐述以下话题:

  • 从通俗语言角度来说,DSL是什么
  • 你正在使用哪种DSL
  • 我们为什么需要使用DSL
  • 我们如何去编写自己的DSL
  • 基本示例说明

什么是DSL

维基百科上有如下解释:

DSL(domain-specific language),是一门特定于某个应用领域的计算机语言。这和通用语言(GPL,general-purpose-language)形成对比,通用语言广泛应用于多个领域
我靠,你是认真的吗?(译注:说人话)

通俗来说,DSL为你提供了一种针对于任何特定语言的灵活工具,从而使得特定编程语言发挥更大的作用。

你正在使用哪种DSL

如果你是一名Android开发者,并且正在项目中使用kotlin,你可能正在项目有意地或者无意地使用DSL。你能想起哪些例子吗?(译注:像koin、DBFlow这些第三方框架都在普遍使用DSL)

让我来帮帮你,你是否曾经编写过如下代码:

yourlist.forEach { //your code is here }

上面的实例中,你正在使用forEach来遍历列表。forEach就是kotlin中的一种DSL示例。

我们为什么要使用DSL

我们可以使用DSL来简化和完善应用的复杂度,并且使得代码易读。

我们如何来编写自己的DSL

在介绍编写我们自己的DSL之前,我们需要学习带接收者的lambda表达式(lambda with receiver)。笔者强烈推荐阅读此篇文章 ,不过我现在也可对带接收者的lambda表达式做一个简短的描述。

我们有如下一个buildString函数:

fun buildString(action: (StringBuilder).() -> Unit): String {
    val stringBuilder = StringBuilder()
    action(stringBuilder)
    return stringBuilder.toString()
}

(译注:在kotlin.text包下,有个同名内联函数buildString,实现如下:

public inline fun buildString(builderAction: StringBuilder.() -> Unit): String =
    StringBuilder().apply(builderAction).toString()
)

这里,buildString函数需要一个action作为形参。而action函数自身需要接收一个StringBuilder来作为参数,并且返回一个String类型的值。

现在,我们就可以这样使用buildString:

buildString {
    append("<")
    append("MindOrks")
    append(">")
}

由于我们在buildString函数中使用了扩展函数,所以我们可以使用StringBuilder类相关的属性。

我们同样可以创建自定义的DSL用法

Infix

在kotlin中,使用infix(可译作中缀表达式或中缀函数)可以帮助我们创建自定义的DSL,就好像我们在编写英文一样。比如,

在英文中,我们说“1 plus 2”从而来得到两者之和或者说“1 minus 2”来得到两者之差。在kotlin中我们可以使用中缀调用来实现同样的效果。

我们来创建一个中缀函数用于两数相加:

infix fun Int.plus(num: Int) = this + num

我们在这里创建了Int的一个扩展函数,将传入的参数和自身进行相加。所以,我们可以这样使用该中缀函数:

val output = 1 plus 2

在上面的代码中,使用我们创建的中缀函数plus来产生结果。当在Logcat中打印时,将会打印和为3.

这是由于我们创建了一个中缀扩展函数,从而在传统方式中使用“+”之处使用plus。

同样地,如果我们想要创建中缀函数minus,我们可以有如下定义:

infix fun Int.minus(num: Int) = this - num

然后我们就可以这样去使用:

val output = 1 minus 2

中缀调用提高代码的可读性并且更具条理性,易于人类去阅读。所以对于不了解编程语言的人来说,他们同样知道这是在对两个数相加或相减。

Invoke

invoke operator(译注:即(),比如 add(),这就是在调用add函数)允许任何对象像函数一样被调用。让我们先创建一个Student类:

class Student {

    operator fun invoke(student: Student.() -> Unit) = student

    fun addName(name: String) {

    }

    fun addMarks(marks: Int) {

    }
}

在这个类中,我们创建了一个invoke方法,参数为接收类型为Student的lambda,并且返回参数自身。这样,我们就可以在student对象中调用所有Student类的方法。

现在,像通常一样,我们创建一个Student类的实例:

val student = Student()

现在我们可以使用Student类的addName和addMarks方法作为DSL。不过,我们可以有两种不同的方式来使用它:

方式一(传统的方式):

student.addName("MindOrks")
student.addMarks(100)

方式二(DSL方式):

student {
    addName("MindOrks")
    addMarks(100)
}

现在,让我们讨论在Android中使用DSL的实例

1. 数据类(data class)。

在这我们将讨论如何为data class创建一个DSL。假定我们有一个data class Student:

data class Student(var name: String? = null, var age: Int? = null, var marks: Int? = null)

这样,如果我们要使用这个data class,我们会写成:

val student = Student("MindOrks", 20, 30)

现在我们把上面的代码转换成DSL形式,首先我们将会创建一个新lambda:

fun student(student: Student.() -> Unit): Student = Student().apply(student)

这样:

val student = student {
    name = "MindOrks"
    age = 20
    marks = 30
}
2. UI元素

我们将TextView作为UI元素。这样:

textView.text = "MindOrks"
textView.setOnClickListener {

}
textView.setTextColor(Color.BLACK)

但是我们可以以DSL的方式使用它:

textView.apply {
    text = "MindOrks"
    setOnClickListener {

    }
    textColor(Color.BLACK)
}

我们可以使用apply函数为任何一个UI元素创建DSL。

3. JSON

在Android中为了创建一个JSON我们会这样使用:

val jsonObject = JSONObject()
jsonObject.put("name","MindOrks")
jsonObject.put("age",20)

这就是我们传统的创建JSON对象的方式。

现在,让我们看看如何创建用来生成JSON对象的DSL。首先,我们将会创建一个类,让它继承自JSONObject:

class Json() : JSONObject() {

}

此时此刻,我们将再创建一个参数为带接收者的lambda的构造器。

constructor(json: Json.() -> Unit) : this() {
    this.init()
}

紧接着,我们利用中缀函数来创建规范,用于往JSON对象中添加值。

infix fun  String.to(value: T) {
    put(this, value)
}

现在,类的整体结构如下:

class Json() : JSONObject() {

constructor(json: Json.() -> Unit) : this() {
    this.json()
}

infix fun  String.to(value: T) {
    put(this, value)
}

}
那么我们就可以在Activity中使用DSL来创建JSON:

val json = Json {
    "name" to "MindOrks"
    "age" to 20
}

在这里:

  • to, 用于向JSON对象中添加value的中缀函数
  • 我们创建的JSON类,它将键值对用于创建JSON对象
    在Logcat中将输出:

{“name”:“MindOrks”,“age”:20}

学习愉快!

你可能感兴趣的:(android,kotlin,dsl)