一、领域特定语言 DSL的概念
DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言。由于它是以简洁的形式进行表达,整体上直观易懂,使得调用代码和读代码的成本都得以降低,即使是不懂编程语言的一般人都可以进行使用。比如大家耳熟能详的 SQL 和正则表达式、软件构建领域 Ant、UI 设计师 HTML。所谓领域也就是限定语言是适用于一定范围的。CSS 专注定义我们页面结构和样式,包括颜色和尺寸等等,而 SQL 专注于对数据的操作。
DSL 分为外部 DSL
和内部 DSL
。
外部DSL:在主程序设计语言之外,用一种单独的语言表示领域专有语言。可以是定制语法,或者遵循另外一种语法,如 XML、JSON。(从零开始构建的语言,需要实现语法解析器等)
内部 DSL:通常是基于通用编程语言实现,具有特定的风格,如 iOS 的依赖管理组件 CocoaPods 和 Android 的主流编译工具 Gradle。(从一种宿主语言构建而来)
通用编程语言和DSL的区别:
通用编程语言(如 Java、Kotlin、Android等),往往提供了全面的库来帮助开发者开发完整的应用程序,而 DSL 只专注于某个领域,比如 SQL 仅支持数据库的相关处理,而正则表达式只用来检索和替换文本,我们无法用 SQL 或者正则表达式来开发一个完整的应用。
DSL 与通用编程语言的区别:
DSL 供非程序员使用,供领域专家使用;
DSL 有更高级的抽象,不涉及类似数据结构的细节;
DSL 表现力有限,其只能描述该领域的模型,而通用编程语言能够描述任意的模型;
二、Kotlin的DSL特性支持
许多现代语言为创建内部 DSL 提供了一些先进的方法, Kotlin 也不例外。
在Kotlin 中创建 DSL , 一般主要使用下面两个特性:
- 扩展函数、扩展属性。
- 带接收者的 Lambda 表达式(高阶函数)。
1.例子1
在日常开发中,我们使用DSL语言主要在gradle文件中:
dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'androidx.work:work-runtime:2.3.4'
}
gradle就知道我们到底依赖了哪些库。使用kotlin来进行实现:
-
首先创建DSL.kt文件,我们在里面定义Dependency类:
class Dependency { // 定义个 list 来保存所有依赖库 val libraries = ArrayList<String>() // 定义向 list 中添加依赖库的方法 fun implementation(lib: String) { libraries.add(lib) } }
-
如果是常规用法,我们会这样写:
val dependency = Dependency() dependency.implementation("lib1") dependency.implementation("lib2")
-
如果你再熟悉一点Kotlin,可以优化一下:
val dependency = Dependency().apply{ implementation("lib1") implementation("lib2") }
目前的代码结构已经很接近DSL语法了,如果我们能把Dependency()的创建隐藏起来就更完美了。
-
接下来定义一个 dependencies 高阶函数如下:
// 把函数类型参数定义到 Dependency 类中,调用它时先创建实例,通过实例调用函数类型参数 // 传入的 Lambda 表达式就能得到执行,最后把保存的依赖库集合返回 fun dependencies(block: Dependency.() -> Unit): List<String> { val dependency = Dependency() dependency.block() return dependency.libraries }
-
用法
val dependency = dependency{ implementation("lib1") implementation("lib2") }
2.例子2
对OkHttp做一下简单的封装,只是为了演示如何实现dsl。
class RequestWrapper {
var url: String? = null
var method: String? = null
var body: RequestBody? = null
var timeout: Long = 10
internal var _success: (String) -> Unit = { }
internal var _fail: (Throwable) -> Unit = {}
fun onSuccess(onSuccess: (String) -> Unit) {
_success = onSuccess
}
fun onFail(onError: (Throwable) -> Unit) {
_fail = onError
}
}
fun http(init: RequestWrapper.() -> Unit) {
val wrap = RequestWrapper()
wrap.init()
executeForResult(wrap)
}
private fun executeForResult(wrap: RequestWrapper) {
Flowable.create<Response>({ e ->
onExecute(wrap)?.let { e.onNext(it) }
}, BackpressureStrategy.BUFFER)
.subscribeOn(Schedulers.io())
.subscribe(
{ resp ->
wrap._success(resp.body()!!.string())
},
{ e -> wrap._fail(e) })
}
private fun onExecute(wrap: RequestWrapper): Response? {
var req: Request? = null
when (wrap.method) {
"get", "Get", "GET" -> req = Request.Builder().url(wrap.url).build()
"post", "Post", "POST" -> req = Request.Builder().url(wrap.url).post(wrap.body).build()
"put", "Put", "PUT" -> req = Request.Builder().url(wrap.url).put(wrap.body).build()
"delete", "Delete", "DELETE" -> req =
Request.Builder().url(wrap.url).delete(wrap.body).build()
}
val http = OkHttpClient.Builder().connectTimeout(wrap.timeout, TimeUnit.SECONDS).build()
val resp = http.newCall(req).execute()
return resp
}
封装完OkHttp之后,看看如何来编写get请求:
http {
url = "http://www.baidu.com/"
method = "get"
onSuccess {
string -> L.i(string)
}
onFail {
e -> L.i(e.message)
}
}
总结:DSL语法可有帮助我们节省很多的代码,让代码看上去更规范。
网友评论