美文网首页
Kotlin 领域专用语言DSL

Kotlin 领域专用语言DSL

作者: 大鹏的鹏 | 来源:发表于2019-08-15 14:55 被阅读0次

    一、领域特定语言 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来进行实现:

    1. 首先创建DSL.kt文件,我们在里面定义Dependency类:

      class Dependency {
        // 定义个 list 来保存所有依赖库
        val libraries = ArrayList<String>()
      
        // 定义向 list 中添加依赖库的方法
        fun implementation(lib: String) {
            libraries.add(lib)
        }
      }
      
    2. 如果是常规用法,我们会这样写:

      val dependency = Dependency()
      dependency.implementation("lib1")
      dependency.implementation("lib2")
      
    3. 如果你再熟悉一点Kotlin,可以优化一下:

      val dependency = Dependency().apply{
          implementation("lib1")
          implementation("lib2")
      }
      

      目前的代码结构已经很接近DSL语法了,如果我们能把Dependency()的创建隐藏起来就更完美了。

    4. 接下来定义一个 dependencies 高阶函数如下:

        // 把函数类型参数定义到 Dependency 类中,调用它时先创建实例,通过实例调用函数类型参数
        // 传入的 Lambda 表达式就能得到执行,最后把保存的依赖库集合返回
        fun dependencies(block: Dependency.() -> Unit): List<String> {
            val dependency = Dependency()
            dependency.block()
            return dependency.libraries
        }
      
    5. 用法

      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语法可有帮助我们节省很多的代码,让代码看上去更规范。

    相关文章

      网友评论

          本文标题:Kotlin 领域专用语言DSL

          本文链接:https://www.haomeiwen.com/subject/deezjctx.html