使用DSL增加代码的可读性

最近学习了一下kotlin的DSL特性,感觉挺有趣的,可以极大的提升代码的可读性。DSL是domin specific language的缩写,中文名叫做领域特定语言,具体涉及到lambda表达式,invoke约定,中缀表达式,扩展函数这些技术细节。
如下所示,我们可以将代码写成这样,这段代码我们可以很清晰的看出执行了一个异步操作,在异步模块内,创建了一个json对象,然后再转到UI线程上执行。

doAsync {
    
    
    val json = JsonNode {
    
    
        "test1" to 1
        "test2" to 2
        "test3" to true
        "test4" {
    
    
            "key1" to 1.0
            "key2" {
    
    
                "key3" to 345
            }
        }
        "test5" to array(1,2,3,4,5)
    }
    println(json.toString())

    uiThread {
    
    
        this.button.setText("执行完毕")
        this.alert("哈哈哈哈哈")
    }
}

1.具体实现

1.1 lambda表达式

kotlin当中的lambda表达式,支持当最后一个参数是方法时,可以直接写在括号外,如果只有一个方法参数时,可以省略括号。所以我们就可以写出这样的两个方法:

fun doAsync(block:()->Unit){
    
    
    Thread(Runnable(block)).start()
}

fun uiThread(block: () -> Unit){
    
    
    this.runOnUiThread(block)
}

一个是异步执行的方法,一个是在ui线程执行的方法,然后调用的时候就可以写成如下方式:

doAsync {
    
    
    //子线程执行的逻辑
    uiThread {
    
    
        //ui线程执行的逻辑
    }
}

对比java,代码简洁了非常多

new Thread(new Runnable() {
    
    
    @Override
    public void run() {
    
    
        //子线程执行的逻辑
        runOnUiThread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //ui线程执行的逻辑  
            }
        });
    }
}).start();

1.2 invoke约定+中缀表达式+扩展函数

目前很多库都已经将DSL玩出了花,比如:
html : https://github.com/Kotlin/kotlinx.html
可以非常直观的创建html,一目了然

html {
    
    
    body {
    
    
        div {
    
    
            a("https://kotlinlang.org") {
    
    
                target = ATarget.blank
                +"Main site"
            }
        }
    }
}

anko:https://github.com/Kotlin/anko
可以通过这种方式来写界面

verticalLayout {
    
    
    val name = editText()
    button("Say Hello") {
    
    
        onClick {
    
     toast("Hello, ${
      
      name.text}!") }
    }
}

得到如下的界面
在这里插入图片描述
其实这些库的实现方式就是用了 invoke约定,中缀表达式以及扩展函数。

我尝试着使用DSL方式实现创建json对象.
首先分析一下:

  1. 一串json其实就是一个Map,key是字符串,value可以是字符串,整型等基础数据,也可以是一个数组或者json对象,所以我们需要一个Map<String, Any>来存储这些数据;
  2. 我们想要实现"test" : “value”,这种对应关系,所以需要实现一个中缀表达式,然后将这个对应关系转换成entry,添加到map里面;
  3. 想要实现如下的方式写json,所以我们得针对String写一个invoke方法,能够将String变成key,方法体里面的内容变成value,存到map里。
"test1" {
    
    
  "key2" to 1.0
}

经过分析,我们可以写出如下代码:

class Node {
    
    

    val body: HashMap<String, Any> = HashMap()

    fun toJson(): JSONObject {
    
    
        val jsonObject = JSONObject()
        body.forEach {
    
    
            if (it.value is Node) {
    
    
                jsonObject.put(it.key, (it.value as Node).toJson())
            } else {
    
    
                jsonObject.put(it.key, it.value)
            }
        }
        return jsonObject
    }

    //invoke约定和扩展方法
    inline operator fun String.invoke(block: Node.() -> Unit): Node {
    
    
        val node = Node()
        node.block()
        this@Node.body[this] = node
        return node
    }

    //中缀表达式
    infix inline fun <T> String.to(value: T) {
    
    
        this@Node.body[this] = value!!
    }
}

fun JsonNode(block: Node.() -> Unit): JSONObject {
    
    
    val node = Node()
    node.block()
    return node.toJson()
}

fun <T> array(vararg values: T): JSONArray {
    
    
    val jsonArray = JSONArray()
    for (value in values) {
    
    
        jsonArray.put(value)
    }
    return jsonArray
}

使用方式如下:

JsonNode {
    
    
    "test1" to 1
    "test2" to 2
    "test3" to true
    "test4" {
    
    
        "key1" to 1.0
        "key2" {
    
    
            "key3" to 345
        }
    }
    "test5" to array(1,2,3,4,5)
}

然后我们就可以得到这样的json脚本

扫描二维码关注公众号,回复: 14641847 查看本文章
{
    
    
    "test4": {
    
    
        "key1": 1,
        "key2": {
    
    
            "key3": 345
        }
    },
    "test5": [
        1,
        2,
        3,
        4,
        5
    ],
    "test2": 2,
    "test3": true,
    "test1": 1
}

猜你喜欢

转载自blog.csdn.net/hbdatouerzi/article/details/124302147