【Kotlin精简】第5章 简析DSL

1 DSL是什么?

Kotlin 是一门对 DSL 友好的语言,它的许多语法特性有助于 DSL 的打造,提升特定场景下代码的可读性和安全性。本文将带你了解 Kotlin DSL 的一般实现步骤,以及如何通过 @DslMarkerContext Receivers 等特性提升 DSL 的易用性。

DSL 全称是 Domain Specific Language,即领域特定语言。顾名思义 DSL 是用来专门解决某一特定问题的语言,比如我们常见的 SQL 或者正则表达式等,DSL 没有通用编程语言(Java、Kotlin等)那么万能,但是在特定问题的解决上更高效。

2 Gradle Kotlin DSL的优点和使用

Gradle Kotlin DSLGradle 5.0引入的一种新型的Gradle脚本语言,作为Groovy语言的替代方案。
官方文档中提到,Kotlin DSL具有如下的优点:

  1. 类型安全:编写Gradle脚本时,可以进行静态类型检查,这样可以保证更高的代码质量和更好的可维护性;
  2. 代码提示:Kotlin语言具有良好的编码体验,比如IDE可以提示代码补全、语法错误等,这些在Groovy语言中不易得到;
  3. 使用简单:Kotlin是一种现代化的语言,语法易懂,学习成本低;
  4. 高效性:Gradle使用Kotlin编写的DSL脚本会比同样的Groovy脚本快2~10倍。

创作一套全新新语言的成本很高,所以很多时候我们可以基于已有的通用编程语言打造自己的 DSL,比如日常开发中我们将常见到 gradle 脚本 ,其本质就是来自 Groovy 的一套 DSL

android {compileSdkVersion 28defaultConfig {applicationId "com.my.app"minSdkVersion 24targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "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 及其优势

KotlinAndroid 的主要编程语言,因此我们可以在 Android 开发中发挥其 DSL 优势,提升特定场景下的开发效率。例如 ComposeUI 代码就是一个很好的示范,它借助 DSLKotlin 代码具有了不输于 XML 的表现力,同时还兼顾了类型安全,提升了 UI 开发效率。

3.1 一个简单DSL例子

Kotlin中实现DSL构建要依靠这几样东西:

  1. 扩展函数;
  2. 带接收者的 Lambda 表达式;
  3. 在方法括号外使用Lambda

我们先来看一下一个DSL例子:

val person = person {name = "John"age = 25address {street = "Main Street"number = 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 = "John"it.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 = "John"age = 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 = "John"age = 25address {street = "Main Street"number = 42city = "London"}
}

3.2 实现简单的UI布局

我们先来看下这个布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/tv"android:layout_width="match_parent"android:layout_height="match_parent"android:textSize="16sp"android:paddingTop="10dp" /></FrameLayout>

上面XML使用DSL写法如下:

context.FrameLayout {layout_width = match_parentlayout_height = wrap_contentTextView {layout_id = "tv"layout_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 ?: 0updateLayoutParams<ViewGroup.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()updateLayoutParams<ViewGroup.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 等事件处理
  • 如需要还可以嵌入 iffor 这样的控制语句

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,我们可以提高代码的可读性、灵活性和类型安全性。当然 AndroidDSL 远不止这些使用场景 ,但是实现思路都是相近的,最后再来一起回顾一下:

  1. DSL 是什么?
    DSL 是一种针对特殊编程场景的语言或范式,它处理效率更高,且表达式更为专业。
    例如 SQL、HTML、正则表达式等。
  2. Kotlin 如何支持 DSL
    通过 扩展函数、带接收者的函数类型等来支持使用 DSL。
  3. Kotlin 自定义 DSL 的优势
    提供一套编程风格,可以简化构建一些复杂对象的代码,提高简洁程度的同时,具备很高的可读性。
  4. Kotlin 自定义 DSL 的缺点
    构造代码较为复杂,有一定上手难度,非必要不使用。

Tips: 对于顶级的Android发烧友,或者是Kotlin学习爱好者可以深度去挖掘DSL,或者是高级的Kotlin语法糖。注意对于在职场打拼的各位朋友们,还是那句话:学值得变现的知识点,并且要等机会来变现,从这个角度,Kotlin会用就可以了,不一定要非要死磕语法糖。切记。职场和自由职业free style 学习的东西是不一样的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/119599.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[UDS] --- DiagnosticSessionControl 0x10 service

1 会话 $10包含3个子功能&#xff0c;01 Default默认会话&#xff0c;02 Programming编程会话&#xff0c;03 Extended扩展会话&#xff0c;ECU上电时&#xff0c;进入的是默认会话&#xff08;Default&#xff09;。 为什么设计三个会话模式呢&#xff1f;因为权限问题。默认…

CSS高级的详细解析

CSS高级 目标&#xff1a;掌握定位的作用及特点&#xff1b;掌握 CSS 高级技巧 01-定位 作用&#xff1a;灵活的改变盒子在网页中的位置 实现&#xff1a; 1.定位模式&#xff1a;position 2.边偏移&#xff1a;设置盒子的位置 left right top bottom 相对定位 posit…

【计算机网络(1)】计算机网络体系结构1:计算机网络概述

文章目录 概念 & 功能 & 发展计算机网络的概念计算机网络的功能计算机网络的发展网络的本质 组成 & 分类计算机网络的组成计算机网络的分类 概念 & 功能 & 发展 计算机网络的概念 1. 网络 网一样的东西或网状系统。其中&#xff08;有线电视网络、电信网…

34基于MATLAB的杨氏双孔干涉条纹,可调节距离,孔的大小等参数,程序已调试通过,可直接运行。

基于MATLAB的杨氏双孔干涉条纹&#xff0c;可调节距离&#xff0c;孔的大小等参数&#xff0c;程序已调试通过&#xff0c;可直接运行。 34matlab杨氏双孔干涉条纹GUI (xiaohongshu.com)

(ubuntu)安装nginx

文章目录 前言回顾Linux命令在线安装&#xff1a;相关命令&#xff1a;相关路径常用配置&#xff1a; 卸载nginxbug相关: 前言 提示&#xff1a;别再问我的规划是什么了&#xff1a;呼吸&#xff0c;难道不算一个吗&#xff1f; --E.M齐奥朗 回顾Linux命令 # 查看当前进程的所…

Go语言入门心法(十三): 反射认知升维

Go语言入门心法(一): 基础语法 Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 Go语言入门心法(四): 异常体系 Go语言入门心法(五): 函数 Go语言入门心法(六): HTTP面向客户端|服务端编程 Go语言入门心法(七): 并发与通道 Go语言入门心法(八): mysql驱动安装报错o…

Win安装protobuf和IDEA使用protobuf插件

一、Win安装protobuf 1、下载编译器 protobuf下载地址&#xff1a;https://github.com/protocolbuffers/protobuf/releases 选择自己需要的版本下载&#xff0c;这里下载的是 protoc-3.19.1-win64.zip&#xff0c;下载之后进行解压即可。 2、配置环境变量 path 系统变量中添加…

RT-Thread 7. RT-Thread Studio ENV修改MCU型号

1. 修改MCU型号 2.在ENV界面输入 scons -c scons --dist3. dist下为更新后完整源代码 4.导入RT-Thread Studio 发现GD32F330已经生效了。 5. 自己编写startup_gd32f3x0.S&#xff0c;准确性待验证 ;/* ; * Copyright (c) 2006-2021, RT-Thread Development Team ; * ; * SPD…

如何将Mysql数据库的表导出并导入到另外的架构

如何将Mysql数据库的表导出并导入到另外的架构 准备一、解决方法1.右键->导出->用mysqldump导出2.注意路径一般为&#xff1a;C:/Program Files/MySQL/MySQL Server 8.0/bin/mysqldump.exe和导出的sql文件位置3.右键->SQL脚本->运行SQL脚本4.找到SQL脚本并点击确定…

通过实例理解Go Web身份认证的几种方式

在2023年Q1 Go官方用户调查报告[1]中&#xff0c;API/RPC services、Websites/web services都位于使用Go开发的应用类别的头部(如下图)&#xff1a; 我个人使用Go开发已很多年&#xff0c;但一直从事底层基础设施、分布式中间件等方向&#xff0c;Web应用开发领域涉及较少&…

SD-WAN跨境网络专线|跨境访问无忧!让海外SaaS平台与视频会议更稳定轻松的解决方案

在现如今全球化的时代&#xff0c;企业都有布局全球或是有潜力的国家&#xff0c;在海外开分公司必不可少&#xff0c;那与海外合作伙伴进行沟通与合作已经成为企业的常态。但是&#xff0c;访问海外的SaaS平台和进行视频会议时&#xff0c;我们经常会遇到网络不稳定、速度慢的…

idea 中配置 maven

前文叙述&#xff1a; 配置 maven 一共要设置两个地方&#xff1a;1、为当前项目设置2、为新项目设置maven 的下载和安装可参考我之前写过的文章&#xff0c;具体的配置文章中也都有讲解。1、为当前项目进行 maven 配置 配置 VM Options: -DarchetypeCataloginternal2、为新项…

CleanMyMac X免费macOS清理系统管家

近些年伴随着苹果生态的蓬勃发展&#xff0c;越来越多的用户开始尝试接触Mac电脑。然而很多人上手Mac后会发现&#xff0c;它的使用逻辑与Windows存在很多不同&#xff0c;而且随着使用时间的增加&#xff0c;一些奇奇怪怪的文件也会占据有限的磁盘空间&#xff0c;进而影响使用…

心理咨询医院预约和挂号系统

源码下载地址 支持&#xff1a;远程部署/安装/调试、讲解、二次开发/修改/定制 系统分为&#xff1a;患者端、医生端、管理员端。 患者端 医生端 管理员端

JAVA基础(JAVA SE)学习笔记(九)异常处理

前言 1. 学习视频&#xff1a; 尚硅谷Java零基础全套视频教程(宋红康2023版&#xff0c;java入门自学必备)_哔哩哔哩_bilibili 2023最新Java学习路线 - 哔哩哔哩 第三阶段&#xff1a;Java高级应用 9.异常处理 10.多线程 11.常用类和基础API 12.集合框架 13.泛型 14…

虚拟机VMware Workstation Pro安装配置使用服务器系统ubuntu-22.04.3-live-server-amd64.iso

虚拟机里安装ubuntu-23.04-beta-desktop-amd64开启SSH(换源和备份)配置中文以及中文输入法等 ​一、获取Ubuntu服务器版 获取Ubuntu服务器版 二、配置虚拟机 选择Custom(advanced)&#xff1a; 选择Workstation 17.x: 选择“I will install the operating system later.”…

原型制作的软件 Experience Design mac( XD ) 中文版软件特色

​XD是一个直观、功能强大的UI/UX开发工具&#xff0c;旨在设计、原型、用户之间共享材料以及通过数字技术进行设计交互。Adobe XD提供了开发网站、应用程序、语音界面、游戏界面、电子邮件模板等所需的一切。xd mac软件特色 体验设计的未来。 使用 Adobe XD 中快速直观、即取即…

上市公司员工及工资数据(2000-2022年)

参照《经济研究》中毛其淋等&#xff08;2023&#xff09;的做法&#xff0c;团队对上市公司员工、工资数据测算。用上市公司&#xff49;在&#xff54;年的员工人数的对数衡量企业的就业水平&#xff0c;采用企业应付职工薪酬与员工人数的比值衡量企业工资水平 一、数据介绍 …

html内连框架

src:引用页面地址 name&#xff1a;框架标识名称 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <!--iframe src&#xff1a;地址 w-h&#xff…

点云处理【七】(点云配准)

点云处理 第一章 点云数据采集 1.点云配准 点云配准是将两个或多个点云数据集融合到一个统一的坐标系统中的过程。这通常是为了创建一个完整的模型或融合从不同视角采集的数据。 点云配准一般分为粗配准和精配准&#xff0c;粗配准指的是在两幅点云之间的变换完全未知的情况下…