对于Kotlin DSL的简单解析与使用

DSL(领域特定语言)是Kotlin所带来的强大语法特性之一,也是Java中所不存在的功能,JetBrain也基于DSL开发出了众多的开源库,Kotlin的开发者可以使用DSL来重构许多已有的代码,甚至有可能做到彻底抛弃HTML,XML,SQL等代码的地步。

简单介绍DSL

DSL是领域特定语言的英文缩写。那到底什么是领域特定语言?

我们最常使用的领域特定语言就是SQL以及正则表达式,SQL和正则表达式都只能解决它们特定领域内的问题,SQL用于数据库操作,而正则表达式则是用来处理文本字符串,它们也都有自己的语法,但是你无法使用它们在计算机上编写完整的程序;所以它们并不是我们常规意义上理解的“编程语言”,那些有能力在计算机上编写几乎任何程序的编程语言,诸如,Kotlin,Java,Python等等我们有一个专业术语来定义它们,叫做图灵完备语言,而上面介绍的那些DSL就不是图灵完备的。

Kotlin DSL 的特点

Kotlin DSL 利用 Kotlin 语言的灵活性,允许我们创建领域特定语言。它可以让我们编写更简洁、更优雅的代码,同时提高代码可读性。

使用到的Kotlin语言特性

Kotlin DSL的例子

我们来举一个Kotlin DSL的例子,如果我们使用JetBrain构建Android UI的开源库Anko的话,我们可以用DSL重构一份XML代码;我们先来看看XML:

<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:padding="30dp"android:orientation="vertical" ><EditTextandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:hint="Name"android:textSize="24sp" /><EditTextandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:hint="Password"android:textSize="24sp" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Login"android:textSize="26sp" /></LinearLayout>

如果换成DSL来编写如下:

lineatLayout {orientation = LinearLayout.VERTICALpadding = dip(30)editText {hint = "Name"textSize = 24f}.lparam(wrapContent, wrapContent)editText {hint = "Password"textSize = 24f}.lparam(wrapContent, wrapContent)button("Login") {textSize = 26f}.lparam(wrapContent, wrapContent)}

即使你不是Android开发者,也可以轻易的看出两者的异同,XML中的元素:LinearLayout, EditText,Button等的层级嵌套关系和DSL中的完全一至,属性的赋值也是应有尽有;这说明,如果你想把当前的一些编写起来不那么方便的代码,迁移到基于Kotlin DSL的库,大多数情况下其实学习成本并不高,实际上变化的只是一些简单的语法规则。 我们在开始下一节之前,先来看看这一段DSL代码,做一个简单的分析,并提出几个问题。我可以首先先告诉大家一个结论,linearLayout {},editText {},button {},这些东西全部都是Kotlin高阶函数,而orientation和padding这些都是Kotlin中的属性;学习过Kotlin的高阶函数的你应该知道,linearLayout {}大括号的内部实际上是一个lambda表达式,它作为一个参数,被传递给了函数linearLayout,而在这个lambda表达式的外部,你是无法引用到orientation和padding属性的,同理,在editText {}的lambda表达式的外部,也是无法引用到hint和textSize属性的。因为orientation是LinearLayout类的属性,而hint和textSize是EditText类的属性;这也就说明在这些lambda表达式的内部,持有了一个对这些类型对象的引用;而这样的lambda表达式就是带接收者的lambda。

深入理解带接收者的lambda

对象调用其对应的类内部的方法,是所有有面向对象编程经验的开发者都知道的原则,但这里要讲清楚带接收者的lambda,还是要从这里讲起。我们先来看下面的例子:

class A {fun function1() {function2()}fun function2() {// do something...}}// 扩展函数fun A.function3() {function1()function2()}fun main(args: Array<String>) {val a = A()a.function1()a.function2()a.function3()}

代码很基础,function1和function2都是A的成员函数,在function1中可以直接调用function2,即在同一个类的方法中可以直接调用另一个方法,而在A的外面,我们则需要创建一个A的对象来调用function1和function2;因为在A的内部,所有的成员(变量/函数)都持有一个A类型对象的引用,而在A的外部,在调用这些成员的时候,我们需要知道调用它的到底是哪一个对象,这是最基本的类和对象之间的关系,我就不再多说了。但在Kotlin中唯一的例外就是扩展函数,在扩展函数中调用其接收者的成员函数(或属性)可以直接调用,这是因为在A的外部调用它的扩展函数,需要一个A的对象。学过高阶函数和lambda编程后我们都知道,函数和lambda在很多时候可以认为是同一种东西,都可以把它们看作是一种有类型的(类型由参数类型,数量,顺序以及返回值类型来确定)可被执行,且可以被保存在一个变量中的代码段;所以带接收者的lambda在某些时候可以认为和扩展函数是等价的(注意,只是某些时候,因为lambda和函数在被编译成.class字节码以后是不同的,这是另一个话题,这里不再展开了),假如我们要定义一个A类型作为接收者类型且一个Int类型作为参数,无返回值的带接收者的lambda,就可以像如下这样定义:

val receiver: A.(Int) -> Until = {// do something...}

如果我们要调用执行这个lambda:

val a = A()a.receiver(3)

所以Part 1中介绍的那些诸如linearLayout {},editText {},button {}这些函数,都是以一个带接收者的lambda作为参数的普通内联函数,让我们以editText {}为例来看看它是如何定义的:

inline fun ViewManager.editText(init: (@AnkoViewDslMarker android.widget.EditText).() -> Unit): android.widget.EditText {return ankoView(`$$Anko$Factories$Sdk25View`.EDIT_TEXT, theme = 0) { init() }}inline fun <T : View> ViewManager.ankoView(factory: (ctx: Context) -> T, theme: Int, init: T.() -> Unit): T {val ctx = AnkoInternals.wrapContextIfNeeded(AnkoInternals.getContext(this), theme)val view = factory(ctx)view.init()AnkoInternals.addView(this, view)return view}

看起来有点复杂,启示拆开来看其实很简单,首先这是一个扩展函数,接收者是ViewManager,这样就限制了这个函数的调用范围,即只能在某个父布局中被调用,随后我们看到参数init就是一个标准的带接收者的lambda,而init在函数内部调用ankoView函数的时候又会在它的lambda参数中被调用,ankoView函数用来生成一个EditText对象,至于内部的原理,我们不去分析,而editText函数又会将这个EditText对象返回,便于函数的调用者获取这个对象的引用;最后我们看到,整个函数加了inline修饰符,即被声明成内联的,这样就保证了DSL API的执行效率,而执行init这个带接收者lambda的ankoView实际上也是ViewManager的扩展函数,而且它也是内联的,这里不再做过多的源码深入。我们简单的体验了一下如何声明一个DSL API,从Anko来看,实际上就是以下三点:

  • 1.使用扩展函数来限制函数的调用范围
  • 2.使用带接收者的lambda来保证API中的嵌套关系
  • 3.使用inline修饰符,把这些有lambda表达式作为参数的函数声明成内联的来保证执行效率 我们这里再详细说一下第二点。 我们在编写HTML和XML的时候,其中一点非常重要,那就是嵌套关系;这些嵌套关系即保证了这些元素之间的包含和被包含的关系,又保证了HTML或XML的可读性;以使用XML来编写Android UI为例,如果不使用XML,而是直接编写Java代码的话,也是可行的,但是我们只能使用Java那种从上到下不停new出一个对象,然后用对象不停调用不同方法的办法来创建UI,当然也是可行的,但是这几乎可以说是让代码的可读性瞬间归零,这样编写代码即容易出错,后期也几乎不可维护。但是现在Kotlin有了带接收者的lambda,我们可以在保留嵌套关系的同时,使用Kotlin这样的图灵完备语言来编写我们需要的UI,这样就实现了Part 1中提到的内部DSL的全部优点。

函数式的对象的invoke约定

Kotlin的约定有很多种,而比如使用便捷的get操作,以及重载运算符等等,invoke约定也仅仅是一种约定而已;我们可以把lambda表达式或者函数直接保存在一个变量中,然后就像执行函数一样直接执行这个变量,这样的变量通常声明的时候都被我们赋值了已经直接定义好的lambda,或者通过成员引用而获取到的函数;但是别忘了,在面向对象编程中,一个对象在通常情况下都有自己对应的类,那我们能不能定义一个类,然后通过构造方法来产生一个对象,然后直接执行它呢?这正是invoke约定发挥作用的地方。

class A(val str: String) {operator fun invoke() {println(str)}}fun main(args: Array<String>) {val a = A("Hello")a()}输出:Hello

我们只需要在一个类中使用operator来修饰invoke函数,这样的类的对象就可以直接像一个保存lambda表达式的变量一样直接调用,而调用后执行的函数就是invoke函数。 我们还有另一种方式来实现可调用的对象,即让类继承自函数类型,然后重写invoke方法:

class A : (String) -> String {override fun invoke(str: String): String {println(str)return str}}fun main(args: Array<String>) {val a = A("Hello")println(a())}输出:HelloHello

直接让一个类继承自函数类型,这样invoke的函数类型就和继承的类型一致了,我们也可以像上面那样直接调用A类的对象,最终会执行invoke函数。 使用invoke约定可以构建出什么样的DSL API呢?在Anko中好像还没有发现这样的例子,但是在Gradle的构建脚本中这样的例子就比较常见:


dependencies.compile("junit:junit:4.11")dependiences {compile("junit:junit:4.11")}

dependiences实际上就是一个对象,它既可以直接调用compile方法,又能在它的lambda表达式参数内调用compile,可见dependiences也是一个使用了invoke约定的类的对象,而它接收的是一个带接收者的lambda表达式作为函数参数。 带接收者的lambda和invoke约定是支撑Kotlin DSL的两大语法特性,但实际上在Kotlin中众多的语法糖中,还有许多特性为你设计DSL的优雅语法提供了可能,这其中包括了:中辍调用,运算符重载,括号外的lambda等等等等;我们不妨充分发散自己的思维,让我们使用这些众多的优雅语法构建一个属于自己的DSL库,用来解决编程中某一类特定领域的棘手问题;Json数据格式也是一个讲究嵌套的数据格式,我们能否充分发挥我们的想象来编写一个基于DSL的库,来对Json做点什么呢?

那些优秀的DSL开源库

下面介绍的Kotlin DSL开源库都是Kotlin的亲爹JetBrain开发的,这说明,就目前来看广大开发者应该还没有把DSL的潜力发挥到极致,如果您有其它优秀的的DSL库推荐,可以给文章留言。

  • 数据库操作:Exposed Exposed是JetBrain推出的,可以使用DSL代替SQL来操作数据库的开源库,项目地址如下:Exposed
  • 动态构建Android UI:Anko Anko也是JetBrain推出的,上文已经提到过了;它是一款便于Android开发者使用Kotlin进行Android开发的函数库,其中,使用DSL动态构建Android UI只是其中的一部分功能,这个库的Github地址如下:Anko
  • 动态构建HTML布局:kotlinx.html 也是JetBrain官方推出的库,用来使用DSL来构建HTML布局,从它的包名中含有kotlinx就可以看出来,它的受重视程度高于Anko,基本上属于Kotlin官方develop kit中的一部分,它的Github地址如下: kotlinx.html 除此之外,Gradle已经支持使用Kotlin DSL来编写构建脚本,使用Gradle的同学,也不妨立刻开始尝试。

本文是对在Kotlin开发语言当中的DSL的一些原理和简单使用解析,对于修学Kotlin当然还有很多的进阶技术点。下面是一些总结,可以在主业点击可以看看详细的内容板块。

最后

Kotlin DSL 是一种强大的工具,可以帮助我们编写更简洁、优雅的代码。通过使用 Kotlin DSL,我们可以提高代码的可读性、灵活性和类型安全性。现在,让我们开始使用 Kotlin DSL,探索编程世界。

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

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

相关文章

Mysql——》int(1)和 int(10)区别

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

零基础打靶—CTF4靶场

一、打靶的主要五大步骤 1.确定目标&#xff1a;在所有的靶场中&#xff0c;确定目标就是使用nmap进行ip扫描&#xff0c;确定ip即为目标&#xff0c;其他实战中确定目标的方式包括nmap进行扫描&#xff0c;但不局限于这个nmap。 2.常见的信息收集&#xff1a;比如平常挖洞使用…

Python标准库:math库【侯小啾python领航班系列(十六)】

Python标准库:math库【侯小啾python领航班系列(十六)】 大家好,我是博主侯小啾, 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ…

【每日一题】找出叠涂元素

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;哈希表 写在最后 Tag 【哈希表】【数组】【2023-12-01】 题目来源 2661. 找出叠涂元素 题目解读 从左往右遍历 arr 给矩阵 mat 上色&#xff0c;在上色的过程中矩阵的某一行或者某一列的全部被上色了&#xff0c;返回…

(C语言)找出1-99之间的全部同构数

同构数&#xff1a;它出现在平方数的右边。例&#xff1a;5是25右边的数&#xff0c;25是625右边的数&#xff0c;即5和25均是同构数。 #include<stdio.h> int main() {for(int i 1;i < 100;i ){if((i*i % 10 i) || (i*i % 100 i))printf("%d\t%d\n",i,…

Java数据结构之《哈希查找》题目

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我写完…

ChatGPT成为“帮凶”:生成虚假数据集支持未知科学假设

ChatGPT 自发布以来&#xff0c;就成为了大家的好帮手&#xff0c;学生党和打工人更是每天都离不开。 然而这次好帮手 ChatGPT 却帮过头了&#xff0c;莫名奇妙的成为了“帮凶”&#xff0c;一位研究人员利用 ChatGPT 创建了虚假的数据集&#xff0c;用来支持未知的科学假设。…

HarmonyOS应用开发——程序框架UIAbility、启动模式与路由跳转

前言 UIAbility简单来说就是一种包含用户界面的应用组件&#xff0c;用于和用户进行交互。每一个UIAbility实例&#xff0c;对应于一个最近任务列表中的任务。 一个应用可以有一个UIAbility&#xff0c;也可以有多个UIAbility。一个UIAbility可以对应于多个页面&#xff0c;建议…

半监督语义分割综述

paper link&#xff1a;https://arxiv.org/pdf/2302.09899.pdf 1. Introduction 图像分割是最古老、研究最广泛的计算机视觉 (CV) 问题之一。图像分割是指将图像划分为不同的非重叠区域&#xff0c;并将相应的标签分配给图像中的每个像素&#xff0c;最终获得ROI区域位置及其类…

线上CPU飙高问题排查!

https://v.douyin.com/iRTqH5ug/ linux top命令 top 命令是 Linux 下一个强大的实用程序&#xff0c;提供了系统资源使用情况的动态、实时概览。它显示了当前正在运行的进程信息&#xff0c;以及有关系统性能和资源利用情况的信息。 以下是 top 命令提供的关键信息的简要概述…

Linux 内核源码各版本下载

下载地址&#xff1a; kernel/git/stable/linux.git - Linux kernel stable treehttps://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/refs/ 1. Linux 内核的基本概念&#xff1a; 内核是什么&#xff1f; 内核是操作系统的核心部分&#xff0c;负责管理系统…

割裂式“多渠道”不是真正的全渠道!浅析全渠道零售和DTC在理念上的不谋而合|徐礼昭

图文&#xff1a;徐礼昭 全渠道零售概念解析 全渠道零售概念由来已久&#xff0c;单纯从业务经营角度&#xff0c;一个品牌在线上线下多个渠道铺货卖货&#xff0c;只能说是多渠道零售&#xff0c;而不是全渠道零售。商派市场负责人徐礼昭认为&#xff0c;品牌企业应该从消费者…

wpf devexpress 使用IDataErrorInfo实现input验证

此处下载源码 当form初始化显示&#xff0c;Register按钮应该启动和没有输入错误应该显示。如果用户点击注册按钮在特定的输入无效数据&#xff0c;form将显示输入错误和禁用的注册按钮。实现逻辑在标准的IDataErrorInfo接口。请查阅IDataErrorInfo接口&#xff08;System.Com…

Fabric:创建应用通道

搭建自定义网络可以参考文章&#xff1a; https://blog.csdn.net/yeshang_lady/article/details/134113296 1 创建通道 网络搭建完成之后&#xff0c;就可以开始创建通道了。Fabric V2.5.4中可以在不创建系统通道的情况下直接创建应用通道。 1.1 修改配置文件 先创建配置文…

【AIGC】接着昨天的AI“洗图”骚操作,继续调戏国产大模型

目录 一、洗稿&#xff0c;洗图&#xff0c;洗视频 二、如何洗图 2.1 先看看效果 2.2 如何做的 2.3 提示词示例 三、试试星火和通义 2.1 星火和通义的特点 2.2 星火的做图能力理解力强&#xff0c;准确度还有待提高 2.3 通义大模型伺候 2.4 这3个大模型可以配合使用 …

结构体||联合体

1.结构体 1.1实际生活中一些东西往往有多个元素组成。如一名学生有身高、体重、名字、学号等。这时候就需要用到结构体。 结构体是一些值的结合&#xff0c;这些值被称为成员变量。结构体的每个成员可以是不同类型的变量&#xff0c;如&#xff1a;标量、数组、指针、甚至是其…

redis——布隆过滤器

一&#xff1a;布隆过滤器是什么&#xff1f; 由一个初值都为零的bit数组和多个哈希函数构成&#xff0c;用来快速判断集合中是否存在某个元素&#xff0c;不保存数据信息&#xff0c;只是在内存中做一个是否存在的标记 二&#xff1a;布隆过滤器能干什么&#xff1f; 高效…

中国湖泊面积-水位长时序数据产品(2000-2020)

今天我们分享中国湖泊面积-水位长时序数据产品&#xff08;2000-2020&#xff09; 该数据集包含中国典型湖泊2000-2020年最大水体面积、多年平均面积、水位变化速率及空间分布矢量。 数据溯源信息 「数据来源描述」Landsat、HJ、ZY、Jason、ENVISAT、Cryosat、ICESat和HY 「数…

dockerfile文件:copy和add 异同

相同点&#xff1a; 复制文件或目录&#xff1a; 无论是 COPY 还是 ADD 都可以将文件或目录从构建上下文复制到容器中。支持源路径和目标路径&#xff1a; 两者都需要指定源路径和目标路径&#xff0c;用于指定要复制的文件或目录在主机上的位置以及在容器中的目标路径。 不同…

RocketMQ事务消息源码解析

RocketMQ提供了事务消息的功能&#xff0c;采用2PC(两阶段协议)补偿机制&#xff08;事务回查&#xff09;的分布式事务功能&#xff0c;通过这种方式能达到分布式事务的最终一致。 一. 概述 半事务消息&#xff1a;指的是发送至broker但是还没被commit的消息&#xff0c;在半…