Kotlin - DSL

一、概念

DSL领取特定语言(Domain Specific Language)。仅为某个小领域而设计的语言,专门来解决特定需求,如 SQL 或正则表达式,功能简单不具备普适性,不同的编程语言都可以用来编写。

寄主语言

用于执行处理 DSL 的语言(Host Language),编写 DSL 的语言和寄主语言可能相同也可能不同。
data class Person(
    var name: String? = null,
    var age: Int? = null,
    var address: Address? = null
)

data class Address(
    var street: String? = null,
    var number: Int? = null,
    var city: String? = null
)

fun person(block: Person.() -> Unit): Person = Person().apply(block)
//提供Address实例创建的扩展函数(定义为扩展函数避免污染Person类)
fun Person.address(block: Address.() -> Unit) {
    address = Address().apply(block)
}

val person = person {
    name = "John"
    age = 25
    address {
        street = "Main Street"
        number = 42
        city = "London"
    }
}

二、使用到的Kotlin语言特性

扩展函数/属性 详见文章

为任意对象增加函数或属性,还能链式调用。(例如不用传参Context)

中缀表达式 详见文章

让函数调用省略.(),实现类似于英语句子的结构增强可读性。

Lambda 详见文章

简写函数,传参使用。

高阶函数:函数形参可以传入Lambda,且最后一个参数是Lambd可以放在括号外面实现了大括号层级。(将对象创建及初始化逻辑封装成带有尾Lambda(最后一个形参是Lambda)的高阶函数中)

带接收者的Lambda:大括号中对 it 的引用变为可以省略掉的 this,让成员函数/属性调用更简洁。

invoke约定:把对象当作函数名一样调用函数,让函数变量像对象调用函数 一样调用自身。

内联函数 详见文章

大量的高阶函数会产生大量的匿名类,使用 inline 减少生成提升性能。(内联函数内部调用的函数必须是public的,对于protected声明的函数使用@PublishedApi即可解决)

变长参数 详见文章

纵向一行写一个要添加的参数。

2.1 扩展函数/属性

//扩展函数
fun Int.days() = Period.ofDays(this)
fun Period.ago() = LocalDate.now() - this
//使用效果
val aa = 123.days().ago()
//扩展属性
val Int.days: Period
    get() = Period.ofDays(this)
val Period.ago: LocalDate
    get() = LocalDate.now() - this
//使用效果
val aa = 123.days.ago

2.2 中缀表达式

//举例一
object ago
infix fun Int.days(ago: ago) = LocalDate.now() - Period.ofDays(this)
//使用效果
val aa = 123 days ago
//举例二
object start
infix fun String.should(start: start): String = ""
infix fun String.with(str: String): String = ""
//使用效果
val aa = "kotlin" should start with "K"

2.3 Lambda特性

2.3.1 简写函数、传参使用

//简写函数
fun sum(x: Int, y: Int): Int {...}
val aa: (Int, Int) -> Int = { x, y -> ... }
//传参使用
fun show(block: (Int, Int) -> Int) {...}
show(aa)

2.3.2 高阶函数

fun layout(block: () -> Unit) {...}
fun button(str: String, block: () -> Unit) {...}
//使用
layout { 
    button("click"){
        ...
    }
}

2.3.3 带接收者的Lambda

fun show(block: StringBuilder.() -> Unit) {}
//使用
show {
    append("aaa")
    reverse()
    insert(1, "b")
}

2.3.4 invoke约定

class Dependencies{
    fun compile(coordinate:String){
        println("add $coordinate")
    }
    operator fun invoke(block:Dependencies.()->Unit){
        block()
    }
}
//使用效果
val dependencies = Dependencies()
//Gradle中用法
dependencies {
    compile("com.android.support:appcompat-v7:27.0.1")
}
//等价于
dependencies.compile("com.android.support:appcompat-v7:27.0.1")

三、构建器模式

实际开发中,类中属性会有val限制后期改变,因此只能调用构造函数来创建对象。
data class Person(
    val name: String,
    val birthday: Date,
    val address: Address?
)

data class Address(
    val street: String,
    val number: Int,
    val city: String
)

class PersonBuilder {
    //构建器中提供和Person相同的属性用来后期赋值(构建数据)
    var name = ""
    var birthday: String = ""   //这里使用的是String,提供更可读的方式传参
    private var address: Address? = null    //这里使用private修饰,后期就不能直接对该属性赋值,只能调用提供的address()函数创建对象来赋值
    //将传参的String类型转为Date
    private var _birthday: Date = Date().apply {
        SimpleDateFormat("yyy-MM-dd").parse(birthday)
    }
    //提供Person实例的创建函数(Person类中的属性有val修饰,只能调用构造函数创建对象)
    fun build(): Person = Person(name, _birthday, address)
    //提供Address实例的创建函数
    fun address(block: AddressBuilder.() -> Unit) {
        address = AddressBuilder().apply(block).build()
    }
}

class AddressBuilder {
    var street: String = ""
    var number: Int = 0
    var city: String = ""
    fun build(): Address = Address(street, number, city)
}

fun person(block: PersonBuilder.() -> Unit): Person = PersonBuilder().apply(block).build()

val person = person {
    name = "John"
    birthday = "2000-10-01"
    address {
        street = "Main Street"
        number = 42
        city = "London"
    }
}

四、集合

当类中属性存在一个或多个值时(集合)。

4.1 并行添加

data class Person(
    private val addresses: List<Address>
)

data class Address(
    val street: String
)

class PersonBuilder {
    private val addresses = mutableListOf<Address>()
    fun address(block: AddressBuilder.() -> Unit) {
        addresses.add(AddressBuilder().apply(block).build())    //往集合中添加新地址
    }
    fun build(): Person = Person(addresses.toList()) //将集合传递给构造
}

class AddressBuilder {
    var street: String = ""
    fun build(): Address = Address(street)
}

fun person(block: PersonBuilder.() -> Unit): Person = PersonBuilder().apply(block).build()

val person = person {
    //一个人有多个地址(并行添加)
    address {
        street = "八一路"
    }
    address {
        street = "人民路"
    }
}

4.2 合并添加

data class Person(
    private val addresses: List<Address>
)

data class Address(
    val street: String
)

class PersonBuilder {
    private val addresses = mutableListOf<Address>()
    fun addresses(block: ADDRESS_LIST.() -> Unit) = addresses.addAll(ADDRESS_LIST().apply(block))
    fun build(): Person = Person(addresses)
}

class AddressBuilder {
    var street: String = ""
    fun build(): Address = Address(street)
}

//自定义List类,全大写强调是一个辅助类,将在SDL中不可见,实现一个很好的结构。
class ADDRESS_LIST : ArrayList<Address>() {
    fun address(block: AddressBuilder.() -> Unit) {
        add(AddressBuilder().apply(block).build())    //往自身添加Address对象
    }
}

fun person(block: PersonBuilder.() -> Unit): Person = PersonBuilder().apply(block).build()

val person = person {
    //一个人有多个地址(合并添加)
    addresses {
        address {
            street = "八一路"
        }
        address {
            street = "人民路"
        }
    }
}

五、缩小作用域 @DslMarker

         属性和方法在内部有意义,在内部的内部无意义,防止API污染。

        由于嵌套的原因,我们可以调用 Lambda 内部每个可用的隐式接收者的函数,因此最终可能不是得到预期的结果。Kotlin v1.1 开始可以通过 @DslMaker 注解来避免这种情况,这时编译器就知道哪些隐式接收者是同一个DSL的一部分,只允许调用最近层的接收者成员。(想要的话仍可以使用显示接收者 this@person.name="李四")

val person = person {    //this:PersonBuilder
    name = "张三"
    addresses {    //this:ADDRESS_LIST
        address {    //this:AddressBuilder
            name = "李四"
        }
    }
}
println(person.name)    //打印:李四
//应用到一个自定义的注释类,然后注释那些DSL类
@DslMarker
annotation class PersonDSL
//除了类,把函数都加上
@PersonDSL
class PersonBuilder {...}
@PersonDSL
class AddressBuilder {...}
@PersonDSL
class ADDRESS_LIST : ArrayList<Address>() {...}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值