用 kotlin 來實現 dsl 風格的編程

Anko

Anko 是一個 DSL (Domain-Specific Language), 它是JetBrains出品的,用 Kotlin 開發的安卓框架。它主要的目的是用來替代之前XML的方式來使用代碼生成UI佈局。html

先來看一個直觀的例子java

<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" />

    <!-- Cannot directly add an inline click listener as onClick delegates implementation to the activity -->
    <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/add_todo" />

</LinearLayout>複製代碼

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

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 的一個主要優勢在於,它須要不多的時間便可理解和傳達某個領域的詳細信息。jquery

簡單封裝OkHttp

OkHttp是一個成熟且強大的網絡庫,在Android源碼中已經使用OkHttp替代原先的HttpURLConnection。不少著名的框架例如Picasso、Retrofit也使用OkHttp做爲底層框架。在這裏我對OkHttp作一下簡單的封裝,其實封裝得有點粗暴只是爲了演示如何實現dsl。android

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

/** * Created by Tony Shen on 2017/6/1. */
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
}複製代碼

封裝完OkHttp以後,看看如何來編寫get請求git

http {

            url = "http://www.163.com/"

            method = "get"

            onSuccess {
                string -> L.i(string)
            }

            onFail {
                e -> L.i(e.message)
            }
        }複製代碼

是否是很像之前用jquery來寫ajax?github

post請求也是相似的,只不過多了bodyajax

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)
            }
        }複製代碼

封裝本身的圖像處理框架

cv4j 是咱們開發的實時圖像處理框架。最先使用濾鏡的方式以下:express

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

後來增長了RxJava封裝的版本apache

RxImageData.bitmap(bitmap).addFilter(new NatureFilter()).into(image);複製代碼

如今Kotlin項目除了可使用上述兩種方式以外,還多了一種方式。

cv4j {
            bitmap = BitmapFactory.decodeResource(resources, R.drawable.test_io)

            filter = NatureFilter()

            imageView = image
        }複製代碼

這個dsl是如何封裝的呢?實際上是對RxJava版本進一步封裝。

/** * Copyright (c) 2017-present, CV4J Contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */
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)
        }
    }

}複製代碼

來看一下程序的最終效果圖

dsl風格使用濾鏡.png

cv4j 目前已經支持了幾十種濾鏡,固然除了濾鏡還有其餘功能,感興趣的童鞋能夠看咱們的源碼:)。

總結

使用dsl的代碼風格,可讓程序更加直觀和簡潔。若是使用Kotlin來開發項目的話,徹底能夠嘗試一下。

公司的sdk項目我也考慮引入Kotlin,我已經寫了一個module用於封裝原先的sdk,這個module只適用於Kotlin項目。用於簡化初始化sdk和實現deep link的註冊服務。

初始化sdk.jpg

註冊各個mLink服務.jpg

能夠感覺一下,使用dsl是否是比原先的代碼更加簡潔和直觀呢?
另外,衆所周知的Gradle也是基於DSL的Java構建工具。

參考資料:

  1. <<使用kotlin寫本身的dsl>>
  2. Type-Safe Builders
相關文章
相關標籤/搜索