DSL(domain-specific language),特定领域语言。wiki 关于 DSL 的定义如下:
A domain-specific language (DSL) is a computer language specialized to a particular application domain. This is in contrast to a general-purpose language (GPL), which is broadly applicable across domains.
通俗来讲,其是指特定领域的语言,如 SQL, Gradle 等。另外其语言是可表达的且易读的。
而 Kotlin 的语言特征,能够让我们更加方便地来实现 DSL。
语言特性
1.Lambda Out of Parentheses
lambda 通常定义的格式为 (list of param types) -> returned type。最简单的格式则为 () -> Unit , 其中 Unit 等同于 Void。
将一个 lambda 赋值给一个变量,最基本的格式如下:
1
|
|
调用此 lambda 的话:
1
|
|
而多参数时,可如下使用:
1 2 |
|
其中参数不使用时,可用下划线来代替。
关于 Lambda 的使用,这里假设有个函数 x(),当一个 lambda 是这个函数的最后一个参数时,其可以放置在函数括号的外面。另外,如果这个 lambda 是这个函数的唯一参数时,这个括号是可以省略的。这样,形如 x({…}) 的使用可以转换为 x(){},再省略括号的话,我们得到 x{}。
lambda 的使用则有如下的形式:
1
|
|
也可以写成单行如下:
1
|
|
这样若是实现一个形如
1 2 3 4 |
|
的 DSL 用法,其中 person 的声明如下:
1 2 3 |
|
这时,则可以定义一个 person 的 lambda 定义:
1 2 3 4 5 |
|
2.Lambdas with receivers
在上述的 person dsl 用法中,it 的使用来说每次都是累赘的。这时可以通过 Lambda with receivers ,来避免每次写它。
它的意思是可以为 lambda 的声明指定一个接受者 receiver ,这样我们在 lambda 中只能访问这个 receiver 的所有非静态的公开函数。由于其限定了 receiver 的域,所以在 lambda 中,可以不必在提供前缀的 it 参数。
所以,这里的格式为 () -> Unit 转变为了 X.()-> Unit。
注意,这里的写法只是用于方便书写,将这两种形式的代码转变为字节码时,可以发现其并没有区别的。仅仅在于其一个赋值给了变量 it,一个赋值给了变量 receiver。
将 person 的 fun 修改为:
1 2 3 4 5 |
|
简写成一行的话如下:
1
|
|
这时,person 的调用便可以简化:
1 2 3 4 |
|
3.Extension functions
此功能即为扩展函数,其表现就是给一些类提供额外的方法,来方便开发调用。其在 Java 中的实现则是通过静态函数来实现,参数便是 Kotlin 中对应的类。
所以,要实现如下的 DSL :
1 2 3 4 5 6 7 8 9 |
|
在声明一个 Address:
1 2 3 |
|
便要对 person 类作拓展:
1 2 3 |
|
4.Builder Pattern
在上面的例子中,其参数都是为 var 定义,若需要为 val 定义,这里可以采用 builder 模式来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
如此,Person 的构造函数便可以使用 val 了,同时 builder 模式保证了类型安全的目的 (type-safe)。
5. Scope control: @DslMarker (Since 1.1)
因为 lambda 的实现都是匿名函数,其可以访问外部作用域。所以这里使用 @DslMarker ,可以达到收窄作用域的目的。如下,使用其声明一个 annotation class:
1 2 |
|
之后,将 @PersonDsl 添加在指定的类上,然后以此类定义的闭包,则不能访问外层作用域的内容。其会在编译器中得到错误的提示。
Anko
在 Android 中关于 Kotlin 的使用,集大成者便属于 Kotlin/anko: Pleasant Android application development 了,其主要包含的内容:
- Anko Commons: 关于 intents、dialogs、 logging 等轻量级的工具库
- Anko Layouts: 提供一个快速且类型安全的快速 Android 布局方法。
- Anko SQLite: 对 Android SQLite 支持的查询和集合转换的 DSL 功能。
- Anko Coroutines: 对 kotlinx.coroutines library 提供的工具类。
其中,以最常用的 Anko Layouts 为例:
1 2 3 4 5 6 |
|
这里指定了一个竖直的 layout,在其中声明了一个名称为 name 的 editText,另有一个文本为 “Say hello” 的按钮,并为按钮添加一个点击的事件,事件可以弹出一个 toast,提示内容为 Hello 加 name 控件的文本。
这里,相比以前的 xml 写法,这种写法简洁了许多。但要使用预览功能,这里需要另外安装 Anko Support Plugin 的插件,并采用 AnkoComponet 的方式书写 Anko Layout。
其中关于 verticalLayout 的定义是在 CustomViews 类中。且此方法针对 ViewManager、Context 及 Activity 做了扩展,以 Actiivty 为例:
1 2 3 4 |
|
其中的 Lambda 为 _LinearLayout 类,关于 VERTICAL_LAYOUT_FACTORY 的定义如下:
1 2 3 4 5 6 7 8 |
|
通过 lambda 的内容,来实例化一个 _LinearLayout,并指定其 orientation 为 LinearLayout.VERTICAL。另外, ankoView 方法,这里所做的工作:
1 2 3 4 5 6 7 |
|
对 context 做以包装,通过 factory 方法得到 View , 再调用 view 的 lambda init 方法。之后通过 AnkoInternals 的 addView 方法对 view 进行添加操作,将其添加至视图中。
回到上面的视图书写中。因为 Android 中的 view 都实现了接口 ViewManager,而这里的扩展方法,针对类便是以 Context、ViewManager、Activity 这三个为主。包含下面的 editText 方法:
1 2 3 4 |
|
其中 editText 方法会调用到 ViewManager 的扩展,紧接着调用到下面的 editText,也就意味着 ankoView 方法的调用,所以在调用 editText 及 button 后,都会执行它们的 addView 方法。 以上,便是简单的通过扩展来实现 view 布局的 DSL 了。另,还有其他更加复杂的扩展可再自行研究了。
Android KTX
android/android-ktx 其定义了一系列关于 Android App 开发中 Kotlin 的扩展,其目的是将我们用 Kotlin 开发 Android 代码更加简化,而并不是对已有的 Android API 添加新的功能。
如:
Kotlin:
1
|
|
Kotlin with Android KTX:
1
|
|
这是一个 Extension functions 的应用,另外还有其他关于 Lambda 等的应用。不过其目前处于一个 preview 的开发应用,可对其未支持的 API 提 pr,进行贡献开发。
参考资料
- Kotlin DSL: From Theory to Practice - DZone Java
- Writing DSLs in Kotlin (part 1) – ProAndroidDev
- Writing DSLs in Kotlin (part 2) – ProAndroidDev
- Higher-Order Functions and Lambdas - Kotlin Programming Language
- Extensions - Kotlin Programming Language
- Type-Safe Groovy-Style Builders - Kotlin Programming Language