一、什么是DSL
DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的 SQL 和正则表达式。
二、DSL的一些常用语法结构
1、简单的Lambda表达式的语法结构
2、指定接受者Receiver的Lambda的表达式,带接收者的 lambda 丰富了函数声明的信息,当传递该 lambda值时,将携带该接收者
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)
}
调用,方法可选
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
}
参考了以下博客,表示感谢!
2、这个库对于学习和理解DSL很有好处
drawable.dsl
3、如何在 kotlin 优雅的封装匿名内部类(DSL、高阶函数)