Kotlin学习-有趣的DSL

423 阅读5分钟

一、什么是DSL

DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的 SQL 和正则表达式。

二、DSL的一些常用语法结构

1、简单的Lambda表达式的语法结构

image.png

2、指定接受者Receiver的Lambda的表达式,带接收者的 lambda 丰富了函数声明的信息,当传递该 lambda值时,将携带该接收者

image.png

3、中缀表达式。infix 修饰符代表该函数支持中缀调用,举个简单的例子:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //调用
        "Student" name "ZhangSan"
    }

    //支持中缀调用的方法
    infix fun String.name(value:String){
        println("$this name $value")
    }

}

打印结果:

System.out: Student name ZhangSan

三、使用DSL的原因和目的

当我们需要灵活配置多项属性的时候DSL就很有用,我们希望得到的就是类型下面这样的简单方便的调用方式(举例):

button.background = shape(Shape.RECTANGLE) {
    corners(8.dp2px)
    padding {
        left = 20.dp2pxInt
        top = 8.dp2pxInt
        right = 20.dp2pxInt
        bottom = 8.dp2pxInt
    }
    solid(ContextCompat.getColor(this@MainActivity, R.color.white))
    stroke {
        width = 2.dp2pxInt
        color = ContextCompat.getColor(this@MainActivity, R.color.colorPrimary)
        //虚线
        dash {
            width = 5.dp2px
            gap = 5.dp2px
        }
    }
}

上面的代码是给Button设置背景,shape方法会返回一个GradientDrawable,通过灵活的配置生成不同效果的背景,这就是DSL函数结合实际使用场景的魅力。参考库drawable.dsl

四、实现一个简单的建造者模式

创建一个Engineer类,动态配置二项:发动机类型和燃油类型。如下:

/**
 * 引擎配置类
 */
class Engineer constructor() {

    internal var engineType: String = ""   //发动机类型
    internal var fuelType: String = ""      //燃油类型

    @JvmOverloads
    constructor(engineType: String, fuelType: String = "") : this()

    constructor(builder: Builder) : this(
        builder.engineType, builder.fuelType
    )

    class Builder internal constructor() {
        var engineType: String = ""
            private set   //禁止直接赋值

        var fuelType: String = ""
            private set   //禁止直接赋值

        fun setEngineType(engineType: String) = apply {
            this.engineType = engineType
        }

        fun setFuelType(fuelType: String) = apply {
            this.fuelType = fuelType
        }

        //参数设置完成后调用build完成最后的动作
        fun build() = Engineer(engineType, fuelType)

    }

}

创建对象的方式,可以通过构造函数,也可以用建造者模式

val engineer0 = Engineer()
val engineer1 = Engineer(engineType = "潍柴")
val engineer2 = Engineer(engineType = "潍柴", fuelType = "柴油")
val engineer3 = Engineer(
    Engineer.Builder()
        .setEngineType("潍柴")
        .setFuelType("柴油")
)
val engineer4 = Engineer.Builder()
    .setEngineType("潍柴")
    .setFuelType("柴油")
    .build()

如果只想通过Builder创建对象,可以将所有的构造函数private

五、实现一个简单的DSL

我们改造上面的建造者模式为DSL形式:

/**
 * 引擎配置类
 */
class Engineer private constructor() {

    internal var engineType: String = ""   //发动机类型
    internal var fuelType: String = ""      //燃油类型

    class Builder internal constructor() {
        //创建对象
        private val engineer = Engineer()
        
        //领域内函数
        fun engineType(engineType: String) = apply {
            engineer.engineType = engineType  //赋值
        }
        
        //领域内函数
        fun fuelType(fuelType: String) = apply {
            engineer.fuelType = fuelType    //赋值
        }

        //build生成结果
        fun build(): Engineer = engineer
    }

}

DSL调用函数

//注意函数中函数作为参数的接收者
fun getEngineer(method: Engineer.Builder.() -> Unit): Engineer {
    val builder = Engineer.Builder()
    builder.method()  //执行领域内函数
    return builder.build()  //调用build方法实现结果
}

使用

getEngineer{
    engineType("潍柴")
    fuelType("柴油")
}

六、DSL封装匿名函数

改造安卓的TextWathcher变成可选实现方法。

1、DSL形式

主要体现DSL的思路,将要配置的参数改造为函数参数的形式。

 class MyTextWatcher : TextWatcher {

    //函数参数对象,注意这种写法
    private var beforeTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null

     private var onTextChanged: ((CharSequence?, Int, Int, Int) -> Unit)? = null

     private var afterTextChanged: ((Editable?) -> Unit)? = null

    /**
     * DSL中使用的函数,领域内函数,供外部调用
     */
    fun beforeTextChanged(method: (CharSequence?, Int, Int, Int) -> Unit) {
        //给函数参数赋值
        beforeTextChanged = method
    }

    fun onTextChanged(method: (CharSequence?, Int, Int, Int) -> Unit) {
        //给函数参数赋值
        onTextChanged = method
    }

    fun afterTextChanged(method: (Editable?) -> Unit) {
        //给函数参数赋值
        afterTextChanged = method
    }

    /**
     * 下面为实现接口重写的方法
     */
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        //函数参数实现具体逻辑
        beforeTextChanged?.invoke(s, start, count, after)
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        //函数参数实现具体逻辑
        onTextChanged?.invoke(s, start, before, count)
    }

    override fun afterTextChanged(s: Editable?) {
        //函数参数实现具体逻辑
        afterTextChanged?.invoke(s)
    }
}

DSL的调用函数

fun TextView.doAddTextChange(method: MyTextWatcher.() -> Unit) {
    val myTextWatcher = MyTextWatcher()
    myTextWatcher.method()
    //将创建的自定义TextWatcher赋值给EditText
    this.addTextChangedListener(myTextWatcher)
}

使用

//动态编写需要的方法,方法可选
et.doAddTextChange {
    beforeTextChanged { charSequence, start, count, after ->

    }

    onTextChanged { charSequence, start, before, count ->

    }

    afterTextChanged { editable ->

    }
}

2、高阶函数形式

用高阶函数实现

inline fun TextView.doAddTextChange(
    //三个函数参数
    crossinline afterTextChanged: (Editable?) -> Unit = {},
    crossinline beforeTextChanged: (CharSequence?, Int, Int, Int) -> Unit = { charSequence, start, count, after -> },
    crossinline onTextChanged: (CharSequence?, Int, Int, Int) -> Unit = { charSequence, start, after, count -> }
) {
    val listener = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            afterTextChanged.invoke(s)  //执行函数参数
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            beforeTextChanged.invoke(s, start, count, after)
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            onTextChanged.invoke(s, start, before, count)
        }
    }
    //匿名函数赋值
    this.addTextChangedListener(listener)
}

调用,方法可选

image.png

Android官方库core_ktx已经帮我们实现了,看看源码:

/**
 * Add an action which will be invoked before the text changed.
 *
 * @return the [TextWatcher] added to the TextView
 */
public inline fun TextView.doBeforeTextChanged(  //用高阶函数进一步封装
    crossinline action: (
        text: CharSequence?,
        start: Int,
        count: Int,
        after: Int
    ) -> Unit
): TextWatcher = addTextChangedListener(beforeTextChanged = action)

/**
 * Add an action which will be invoked when the text is changing.
 *
 * @return the [TextWatcher] added to the TextView
 */
public inline fun TextView.doOnTextChanged(    //用高阶函数进一步封装
    crossinline action: (
        text: CharSequence?,
        start: Int,
        before: Int,
        count: Int
    ) -> Unit
): TextWatcher = addTextChangedListener(onTextChanged = action)

/**
 * Add an action which will be invoked after the text changed.
 *
 * @return the [TextWatcher] added to the TextView
 */
public inline fun TextView.doAfterTextChanged(       //用高阶函数进一步封装
    crossinline action: (text: Editable?) -> Unit
): TextWatcher = addTextChangedListener(afterTextChanged = action)

/**
 * Add a text changed listener to this TextView using the provided actions
 *
 * @return the [TextWatcher] added to the TextView
 */
public inline fun TextView.addTextChangedListener(   //和上面封装的思路一致
    crossinline beforeTextChanged: (
        text: CharSequence?,
        start: Int,
        count: Int,
        after: Int
    ) -> Unit = { _, _, _, _ -> },
    crossinline onTextChanged: (
        text: CharSequence?,
        start: Int,
        before: Int,
        count: Int
    ) -> Unit = { _, _, _, _ -> },
    crossinline afterTextChanged: (text: Editable?) -> Unit = {}
): TextWatcher {
    val textWatcher = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            afterTextChanged.invoke(s)
        }

        override fun beforeTextChanged(text: CharSequence?, start: Int, count: Int, after: Int) {
            beforeTextChanged.invoke(text, start, count, after)
        }

        override fun onTextChanged(text: CharSequence?, start: Int, before: Int, count: Int) {
            onTextChanged.invoke(text, start, before, count)
        }
    }
    addTextChangedListener(textWatcher)

    return textWatcher
}

参考了以下博客,表示感谢!

1、 Kotlin 之美—DSL篇

2、这个库对于学习和理解DSL很有好处
drawable.dsl

3、如何在 kotlin 优雅的封装匿名内部类(DSL、高阶函数)

个人学习笔记