Kotlin DSL詳解

DSL簡介

所謂DSL領域專用語言(Domain Specified Language/ DSL),其基本思想是「求專不求全」,不像通用目的語言那樣目標範圍涵蓋一切軟件問題,而是專門針對某一特定問題的計算機語言。總的來講 DSL 是爲了解決系統(包括硬件系統和軟件系統)構建初期,使用者和構建者的語言模型不一致致使需求收集的困難。html

舉一個具體的例子來講。在構建證券交易系統的過程當中,在證券交易活動中存在許多專業的金融術語和過程。如今要爲該交易過程建立一個軟件解決方案,那麼開發者/構建者就必須瞭解證券交易活動,其中涉及到哪些對象、它們之間的規則以及約束條件是怎麼樣的。那麼就讓領域專家(這裏就是證券交易專家)來描述證券交易活動中涉及的活動。可是領域專家習慣使用他們熟練使用的行業術語來表達,解決方案的構建者沒法理解。若是解決方案的模型構建者要理解交易活動,就必須讓領域專家用雙方都能理解的天然語言來解釋。這種解釋的過程當中,解決方案的模型構建者就理解了領域知識。這個過程當中雙方使用的語言就被稱爲「共同語言」。前端

共同語言稱爲解決方案模型構建者用來表達解決方案中的詞彙的基礎。構建者將這些共同語言對應到模型中,在程序中就是模塊名、在數據模型中就是實體名、在測試用例中就是對象。在上面的描述,若是要成功構建模型,則須要一種領域專家和構建者(也就是一般的領域分析師/業務分析師)都能理解的「共同語言」。若是可以讓領域專家經過簡單的編程方式描述領域中的全部活動和規則,那麼就能在必定程度上保證描述的完整性。DSL 就是爲了解決這些問題而提出的。java

常見的DSL

常見的DSL在不少領域都能看到,例如:react

  • 軟件構建領域 Ant
  • UI 設計師 HTML
  • 硬件設計師 VHDL

DSL 與通用編程語言的區別

  • DSL 供非程序員使用,供領域專家使用;
  • DSL 有更高級的抽象,不涉及相似數據結構的細節;
  • DSL 表現力有限,其只能描述該領域的模型,而通用編程語言可以描述任意的模型;

DSL分類

根據是否從宿主語言構建而來,DSL 分爲:android

  • 內部 DSL(從一種宿主語言構建而來)
  • 外部 DSL(從零開始構建的語言,須要實現語法分析器等)

Android Gradle構建

Groovy是一種運行在JVM虛擬機上的腳本語言,可以與Java語言無縫結合,若是想了解Groovy能夠查看IBM-DeveloperWorks-精通Groovygit

打開Android的build.gradle文件,會看到相似下面的一些語法。程序員

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

經過上面的Android的build.gradle配置文件能夠發現,buildscript裏有配置了repositories和dependencies,而repositories和dependencies裏面又能夠配置各自的一些屬性。能夠看出經過這種形式的配置,咱們能夠井井有條的看出整個項目構建的一些定製,又因爲Android也遵循約定大於配置的設計思想,所以咱們僅僅只需修改須要自定義的部分便可輕鬆個性化構建流程。github

Groovy腳本-build.gradle

在Groovy下,咱們能夠像Python這類腳本語言同樣寫個腳本文件直接執行而無需像Java那樣既要寫好Class又要定義main()函數,由於Groovy自己就是一門腳本語言,而Gradle是基於Groovy語言的構建工具,天然也能夠輕鬆經過腳原本執行構建整個項目。做爲一個基於Gradle的項目工程,項目結構中的settings.gradle和build.gradle這類xxx.gradle能夠理解成是Gradle構建該工程的執行腳本,當咱們在鍵盤上敲出gradle clean aDebug這類命令的時候,Gradle就會去尋找這類文件並按照規則前後讀取這些gradle文件並使用Groovy去解析執行。編程

Groovy語法

要理解build.gradle文件中的這些DSL是如何被解析執行的,須要介紹Groovy的一些語法特色以及一些高級特性,下面從幾個方面來介紹Groovy的一些特色。json

鏈式命令

Groovy的腳本具備鏈式命令(Command chains)的特性,根據這個特性,當你在Groovy腳本中寫出a b c d的時候,Groovy會翻譯成a(b).c(d)執行,也就是將b做爲a函數的形參調用,而後將d做爲形參再次調用返回的實例(Instance)中的c方法。其中當作形參的b和d能夠做爲一個閉包(Closure)傳遞過去。例如:

// equivalent to: turn(left).then(right)
turn left then right

// equivalent to: take(2.pills).of(chloroquinine).after(6.hours)
take 2.pills of chloroquinine after 6.hours

// equivalent to: paint(wall).with(red, green).and(yellow)
paint wall with red, green and yellow

// with named parameters too
// equivalent to: check(that: margarita).tastes(good)
check that: margarita tastes good

// with closures as parameters
// equivalent to: given({}).when({}).then({})
given { } when { } then { }

Groovy也支持某個方法傳入空參數,但須要爲該空參數的方法加上圓括號。例如:

// equivalent to: select(all).unique().from(names)
select all unique() from names

若是鏈式命令(Command chains)的參數是奇數,則最後一個參數會被當成屬性值(Property)訪問。例如:

// equivalent to: take(3).cookies
// and also this: take(3).getCookies()
take 3 cookies

操做符重載

有了Groovy的操做符重載(Operator overloading),==會被Groovy轉換成equals方法,這樣你就能夠放心大膽地使用==來比較兩個字符串是否相等了,在咱們編寫gradle腳本的時候也能夠盡情使用。關於Groovy的全部操做符重載(Operator overloading)能夠查閱:Operator overloading官方教程

委託

委託(DelegatesTo)能夠說是Gradle選擇Groovy做爲DSL執行平臺的一個重要因素了。經過委託(DelegatesTo)能夠很簡單的定製一個控制結構體(Custom control structures),例以下面的代碼。

email {
    from 'dsl-guru@mycompany.com'
    to 'john.doe@waitaminute.com'
    subject 'The pope has resigned!'
    body {
        p 'Really, the pope has resigned!'
    }
}

接下來能夠看下解析上述DSL語言生成的代碼。

def email(Closure cl) {
    def email = new EmailSpec()
    def code = cl.rehydrate(email, this, this)
    code.resolveStrategy = Closure.DELEGATE_ONLY
    code()
}

上述轉換後的DSL語言,先定義了一個email(Closure)的方法,當執行上述步驟1的時候就會進入該方法內執行,EmailSpec是一個繼承了參數中cl閉包裏全部方法,好比from、to等等的一個類(Class),經過rehydrate方法將cl拷貝成一份新的實例(Instance)並賦值給code,code實例(Instance),經過rehydrate方法中設置delegate、owner和thisObject的三個屬性將cl和email二者關聯起來被賦予了一種委託關係,這種委託關係能夠這樣理解:cl閉包中的from、to等方法會調用到email委託類實例(Instance)中的方法,並能夠訪問到email中的實例變量(Field)。DELEGATE_ONLY表示閉包(Closure)方法調用只會委託給它的委託者(The delegate of closure),最後使用code()開始執行閉包中的方法。

Kotlin和anko進行Android開發

anko

Anko 是一個 DSL (Domain-Specific Language), 它是JetBrains出品的,用 Kotlin 開發的安卓框架。它主要的目的是用來替代之前XML的方式來使用代碼生成UI佈局。
下面看一下傳統的xml界面實現佈局文件。

<LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_height="match_parent"
 android:layout_width="match_parent">

 <EditText
 android:id="@+id/todo_title"
 android:layout_width="match_parent"
 android:layout_heigh="wrap_content"
 android:hint="@string/title_hint" />

 <Button
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:text="@string/add_todo" />
</LinearLayout>

使用Anko以後,能夠用代碼實現佈局,而且button還綁定了點擊事件的代碼以下。

verticalLayout {
 var title = editText {
 id = R.id.todo_title
 hintResource = R.string.title_hint
 }
 button {
 textResource = R.string.add_todo
 onClick { view -> {
 // do something here
 title.text = "Foo"
 }
 }
 }
}

能夠看到 DSL 的一個主要優勢在於,它須要不多的代碼便可理解和傳達某個領域的詳細信息。

OkHttp封裝

OkHttp是一個成熟且強大的網絡庫,在Android源碼中已經使用OkHttp替代原先的HttpURLConnection。不少著名的框架例如Picasso、Retrofit也使用OkHttp做爲底層框架。本文使用Kotlin代碼對它進行簡單的封裝,代碼以下:

import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
import java.util.concurrent.TimeUnit


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 -> e.onNext(onExecute(wrap))
 }, 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
}

封裝完後,調用方式以下:

http {
 url = "http://www.163.com/"
 method = "get"
 onSuccess {
 string -> L.i(string)
 }
 onFail {
 e -> L.i(e.message)
 }
 }

能夠看到這種調用方式很像RxJava的流式風格,也很像前端的fetch請求。post的方式相似:

var json = JSONObject()
 json.put("xxx","yyyy")
 ....

 val postBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),json.toString())
 
 http {
 url = "https://......"
 method = "post"
 body = postBody
 onSuccess {
 string -> L.json(string)
 }

 onFail {
 e -> L.i(e.message)}
 }

封裝圖像處理框架

在Android開發時候,選擇圖片加載庫,通常會選擇一些比較經常使用,知名度比較高的庫,這裏介紹一款新的圖像處理框架cv4j ,cv4j 支持使用濾鏡。

CV4JImage cv4jImage = new CV4JImage(bitmap);
 CommonFilter filter = new NatureFilter();
 Bitmap newBitMap = filter.filter(cv4jImage.getProcessor()).getImage().toBitmap();
 image.setImageBitmap(newBitMap);

若是使用的是rxJava方式,還能夠這樣用:

RxImageData.bitmap(bitmap).addFilter(new NatureFilter()).into(image);

下面是使用dsl的方式來封裝。

package com.cv4j.rxjava

import android.app.Dialog
import android.graphics.Bitmap
import android.widget.ImageView
import com.cv4j.core.datamodel.CV4JImage
import com.cv4j.core.filters.CommonFilter

/**
 * only for Kotlin code,this class provides the DSL style for cv4j
 */
class Wrapper {

 var bitmap:Bitmap? = null

 var cv4jImage: CV4JImage? = null

 var bytes:ByteArray? = null

 var useCache:Boolean = true

 var imageView: ImageView? = null

 var filter: CommonFilter? = null

 var dialog: Dialog? = null
}

fun cv4j(init: Wrapper.() -> Unit) {
 val wrap = Wrapper()

 wrap.init()

 render(wrap)
}

private fun render(wrap: Wrapper) {

 if (wrap.bitmap!=null) {

 if (wrap.filter!=null) {
 RxImageData.bitmap(wrap.bitmap).dialog(wrap.dialog).addFilter(wrap.filter).isUseCache(wrap.useCache).into(wrap.imageView)
 } else {
 RxImageData.bitmap(wrap.bitmap).dialog(wrap.dialog).isUseCache(wrap.useCache).into(wrap.imageView)
 }

 } else if (wrap.cv4jImage!=null) {

 if (wrap.filter!=null) {
 RxImageData.image(wrap.cv4jImage).dialog(wrap.dialog).addFilter(wrap.filter).isUseCache(wrap.useCache).into(wrap.imageView)
 } else {
 RxImageData.image(wrap.cv4jImage).dialog(wrap.dialog).isUseCache(wrap.useCache).into(wrap.imageView)
 }
 } else if (wrap.bytes!=null) {

 if (wrap.filter!=null) {
 RxImageData.bytes(wrap.bytes).dialog(wrap.dialog).addFilter(wrap.filter).isUseCache(wrap.useCache).into(wrap.imageView)
 } else {
 RxImageData.bytes(wrap.bytes).dialog(wrap.dialog).isUseCache(wrap.useCache).into(wrap.imageView)
 }
 }

}

關於cv4j更多的介紹能夠查看:cv4j官方介紹

閱讀原文http://click.aliyun.com/m/39992

相關文章
相關標籤/搜索