怎么免费创建百度网站,哈尔滨市建设网,石家庄58同城招聘信息,在线做拓扑图的网站1 DSL是什么#xff1f;
Kotlin 是一门对 DSL 友好的语言#xff0c;它的许多语法特性有助于 DSL 的打造#xff0c;提升特定场景下代码的可读性和安全性。本文将带你了解 Kotlin DSL 的一般实现步骤#xff0c;以及如何通过 DslMarker #xff0c; Context Receivers 等…1 DSL是什么
Kotlin 是一门对 DSL 友好的语言它的许多语法特性有助于 DSL 的打造提升特定场景下代码的可读性和安全性。本文将带你了解 Kotlin DSL 的一般实现步骤以及如何通过 DslMarker Context Receivers 等特性提升 DSL 的易用性。
DSL 全称是 Domain Specific Language即领域特定语言。顾名思义 DSL 是用来专门解决某一特定问题的语言比如我们常见的 SQL 或者正则表达式等DSL 没有通用编程语言Java、Kotlin等那么万能但是在特定问题的解决上更高效。
2 Gradle Kotlin DSL的优点和使用
Gradle Kotlin DSL是Gradle 5.0引入的一种新型的Gradle脚本语言作为Groovy语言的替代方案。 官方文档中提到Kotlin DSL具有如下的优点
类型安全编写Gradle脚本时可以进行静态类型检查这样可以保证更高的代码质量和更好的可维护性代码提示Kotlin语言具有良好的编码体验比如IDE可以提示代码补全、语法错误等这些在Groovy语言中不易得到使用简单Kotlin是一种现代化的语言语法易懂学习成本低高效性Gradle使用Kotlin编写的DSL脚本会比同样的Groovy脚本快2~10倍。
创作一套全新新语言的成本很高所以很多时候我们可以基于已有的通用编程语言打造自己的 DSL比如日常开发中我们将常见到 gradle 脚本 其本质就是来自 Groovy 的一套 DSL
android {compileSdkVersion 28defaultConfig {applicationId com.my.appminSdkVersion 24targetSdkVersion 30versionCode 1versionName 1.0testInstrumentationRunner android.support.test.runner.AndroidJUnitRunner}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro}}
}build.gradle 中我们可以用大括号表现层级结构使用键值对的形式设置参数没有多余的程序符号非常直观。如果将其还原成标准的 Groovy 语法则变成下面这样是下面这样在可读性上的好坏立判
Android(30,DefaultConfig(com.my.app,24,30,1,1.0,android.support.test.runner.AndroidJUnitRunner)
),BuildTypes(Release(false,getDefaultProguardFile(proguard-android-optimize.txt),proguard-rules.pro)
)除了 GroovyKotlin 也非常适合 DSL 的书写正因如此 Gradle 开始推荐使用 kts 替代 gradle其实就是利用了 Kotlin 优秀的 DSL 特性。
3 Kotlin DSL 及其优势
Kotlin 是 Android 的主要编程语言因此我们可以在 Android 开发中发挥其 DSL 优势提升特定场景下的开发效率。例如 Compose 的 UI 代码就是一个很好的示范它借助 DSL 让 Kotlin 代码具有了不输于 XML 的表现力同时还兼顾了类型安全提升了 UI 开发效率。
3.1 一个简单DSL例子
在Kotlin中实现DSL构建要依靠这几样东西:
扩展函数带接收者的 Lambda 表达式;在方法括号外使用Lambda
我们先来看一下一个DSL例子
val person person {name Johnage 25address {street Main Streetnumber 42city London}
}// 数据模型
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)要实现上面的语法糖现在要做的第一件事就是创建一个新文件将保持DSL与模型中的实际类分离。首先为Person类创建一些构造函数。看看我们想要的结果看到Person的属性是在代码块中定义的。这些花括号实际上是定义一个lambda。这就是使用上面提到的三种Kotlin语言特征中的第一种语言特征的地方在方法括号外使用Lambda。
如果一个函数的最后一个参数是一个lambda可以把它放在方法括号之外。而当你只有一个lambda作为参数时你可以省略整个括号。person {…}实际上与person({…})相同。这在我们的DSL中变得更简洁。现在来编写person函数的第一个版本。
// 数据模型
fun person(block: (Person) - Unit): Person {val p Person()block(p)return p
}所以在这里我们有一个创建一个Person对象的函数。它需要一个带有我们在第2行创建的对象的lambda。当在第3行执行这个lambda时我们期望在返回第4行的对象之前该对象获得它所需要的属性。下面展示如何使用这个函数
val person person {it.name Johnit.age 25
}由于这个lambda只接收一个参数可以用它来调用person对象。这看起来不错但还不够完美如果在我们的DSL看到的东西。特别是当我们要在那里添加额外的对象层。这带来了我们接下来提到的Kotlin功能带接受者的Lambda。
在person函数的定义中可以给lambda添加一个接收者。这样只能在lambda中访问那个接收者的函数。由于lambda中的函数在接收者的范围内则可以简单地在接收者上执行lambda而不是将其作为参数提供。
fun person(block: Person.() - Unit): Person {val p Person()p.block()return p
}// 这实际上可以通过使用Kotlin提供的apply函数在一个简单的单行程中重写。
fun person(block: Person.() - Unit): Person Person().apply(block)现在可以将其从DSL中删除:
val person person {name Johnage 25
}到目前为止还差一个Address类在我们想要的结果中它看起来很像刚刚创建的person函数。唯一的区别是必须将它分配给Person对象的Address属性。为此可以使用上面提到的三个Kotlin语言功能中的最后一个扩展函数。
扩展函数能够向类中添加函数而无需访问类本身的源代码。这是创建Address对象的完美选择并直接将其分配给Person的地址属性。这是DSL文件的最终版本
fun person(block: Person.() - Unit): Person Person().apply(block)fun Person.address(block: Address.() - Unit) {address Address().apply(block)
}现在为Person添加一个地址函数它接受一个Address作为接收者的lambda表达式就像对person构造函数所做的那样。然后它将创建的Address对象设置为Person的属性
val person person {name Johnage 25address {street Main Streetnumber 42city London}
}3.2 实现简单的UI布局
我们先来看下这个布局
?xml version1.0 encodingutf-8?
FrameLayout xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentTextViewandroid:idid/tvandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:textSize16spandroid:paddingTop10dp //FrameLayout上面XML使用DSL写法如下
context.FrameLayout {layout_width match_parentlayout_height wrap_contentTextView {layout_id tvlayout_width match_parentlayout_height match_parenttextSize 16fpadding_top 10}}首先要定义一种声明方式来初始化对象所以可以写一个基于Context的扩展函数
inline fun Context.FrameLayout(style: Int? null,init: FrameLayout.() - Unit
): FrameLayout {val frameLayout if (style ! null) FrameLayout(ContextThemeWrapper(this, style)) else FrameLayout(this)return frameLayout.apply(init)
}// 扩展View的layout_width、layout_height等属性
// 其他属性这里不做详解写法同layout_width、layout_height
inline var View.layout_width: Numberget() {return 0}set(value) {val w if (value.dp 0) value.dp else value.toInt()val h layoutParams?.height ?: 0updateLayoutParamsViewGroup.LayoutParams {width wheight h}}inline var View.layout_height: Numberget() {return 0}set(value) {val w layoutParams?.width ?: 0val h if (value.dp 0) value.dp else value.toInt()updateLayoutParamsViewGroup.LayoutParams {width wheight h}}这里的init就是上面说的带接受者的lamba表达式拉所以代码里去实现一个FrameLayout布局就可以这样子拉
context.FrameLayout {layout_width match_parentlayout_height wrap_content
}
而对于子控件TextView举个栗子
inline fun ViewGroup.TextView(style: Int? null,init: AppCompatTextView.() - Unit
): TextView {val textView if (style ! null) AppCompatTextView(ContextThemeWrapper(context, style)) else AppCompatTextView(context)return textView.apply(init).also { addView(it) }
}这样一个简单的动态布局就出来了没想象中那么高级其实就是对扩展函数、高阶函数的运用。
3.3 小结
Kotlin DSL的好处尤其是对View进行特定领域的处理的时候 很有用。
有着近似 XML 的结构化表现力较少的字符串更多的强类型更安全可提取 linearLayoutParams 这样的对象方便复用在布局中同步嵌入 onClick 等事件处理如需要还可以嵌入 if for 这样的控制语句
4 DSL实现的原理
4.1 扩展函数扩展属性
package stringsfun String.lastChar(): Char this.get(this.length - 1)4.2 lambda使用
lambda 表达式定义
高阶函数高阶函数就是以另一个函数作为参数或返回值的函数。
Kotlin 的 lambda 有个规约如果 lambda 表达式是函数的最后一个实参则可以放在括号外面并且可以省略括号
person.maxBy({ p:Person - p.age })// 可以写成
person.maxBy(){p:Person - p.age
}// 更简洁的风格
person.maxBy{p:Person - p.age
}带接收者的 lambda 想一想 File就是带接受者说明这个lambda的对象是File。
4.3 中缀调用 中缀调用是实现类似英语句子结构 DSL 的核心。
4.4 invoke 约定 invoke约定的作用它的作用就是让对象像函数一样调用方法。
class DependencyHandler{//编译库fun compile(libString: String){Logger.d(add $libString)}//定义invoke方法operator fun invoke(body: DependencyHandler.() - Unit){body()}
}//我们有下面的3种调用方式
val dependency DependencyHandler()
//调用invoke
dependency.invoke {compile(androidx.core:core-ktx:1.6.0)
}
//直接调用
dependency.compile(androidx.core:core-ktx:1.6.0)
//带接受者lambda方式
dependency{compile(androidx.core:core-ktx:1.6.0)
}5 总结
Kotlin DSL 是一种强大的工具可以帮助我们编写更简洁、优雅的代码。通过使用 Kotlin DSL我们可以提高代码的可读性、灵活性和类型安全性。当然 Android 中 DSL 远不止这些使用场景 但是实现思路都是相近的最后再来一起回顾一下
DSL 是什么 DSL 是一种针对特殊编程场景的语言或范式它处理效率更高且表达式更为专业。 例如 SQL、HTML、正则表达式等。Kotlin 如何支持 DSL 通过 扩展函数、带接收者的函数类型等来支持使用 DSL。Kotlin 自定义 DSL 的优势 提供一套编程风格可以简化构建一些复杂对象的代码提高简洁程度的同时具备很高的可读性。Kotlin 自定义 DSL 的缺点 构造代码较为复杂有一定上手难度非必要不使用。
Tips 对于顶级的Android发烧友或者是Kotlin学习爱好者可以深度去挖掘DSL或者是高级的Kotlin语法糖。注意对于在职场打拼的各位朋友们还是那句话学值得变现的知识点并且要等机会来变现从这个角度Kotlin会用就可以了不一定要非要死磕语法糖。切记。职场和自由职业free style 学习的东西是不一样的。