【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

携程 Kotlin DSL 开发与实践

  • 2019-09-24
  • 本文字数:4998 字

    阅读完需:约 16 分钟

携程Kotlin DSL开发与实践

每一个 DSL,都是一定意义上专有的语言,这篇文章希望能够用浅显易懂的方式,将 Kotlin DSL 的应用与实践经验分享给大家。希望对你有所启发,能够构建一门属于自己的专有“语言”。

一、简介

DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言。由于它是以简洁的形式进行表达,整体上直观易懂,使得调用代码和读代码的成本都得以降低,即使是不懂编程语言的一般人都可以进行使用,所以近年来频频被提起,颇受关注。


DSL 分为外部 DSL 和内部 DSL。


DSL:在主程序设计语言之外,用一种单独的语言表示领域专有语言。可以是定制语法,或者遵循另外一种语法,如 XML、JSON。


内部 DSL:通常是基于通用编程语言实现,具有特定的风格,如 iOS 的依赖管理组件 CocoaPods 和 Android 的主流编译工具 Gradle。


这里主要分享在 Kotlin 中构建使用 DSL。

二、应用

Kotlin DSL 的应用广泛,包括 gradle 编写、编写 js、html、SQL 等。下面列举几个使用场景:

2.1 Trip.com 支付网络封装实践

在编写网络代码时,出现频率最高的就是 request 配置和大篇幅的 response 回调处理,那么这两部分的代码该如何优化?在 Trip.com 支付中利用 kotlin DSL 对网络进行二次封装,针对以上问题进行解决。


定义 request 配置,使得最终在做 request 配置时更为简洁:


fun requestBean(request: () -> BusinessBean) {    payClientBuilder.setRequestBean(request())}fun needRetry(needRetry: () -> Boolean) {    payClientBuilder.setNeedRetry(needRetry())}......
复制代码


定义回调模版,解决以下问题:部分网络请求,我们不关心结果,或者不关心 onFailed 的场景,避免掉这部分的冗余代码:


private var callSubSuccess: ((T) -> Unit)? = nullprivate var callSubFailed: ((Client.Error?) -> Unit)? = nullprivate var subCallback: PayNetCallback<T> = object : PayNetCallback<T> {    override fun onSucceed(response: T) {        callSubSuccess?.invoke(response)    }
override fun onFailed(error: Client.Error?) { callSubFailed?.invoke(error) }}fun subSuccess(subSuccess: (T) -> Unit) { callSubSuccess = subSuccess payClientBuilder.setSubCallBack(subCallback)}
fun subFailed(subFailed: (Client.Error?) -> Unit) { callSubFailed = subFailed payClientBuilder.setSubCallBack(subCallback)}
复制代码


预定义扩展函数


object PayNetworkClient {    fun <T : BusinessBean> init(        costClass: Class<T>,        config: PayClientConfigBuilder<T>.() -> Unit    ): PayClientBuilder.NetworkClient? {        var networkClient: payClientBuilder.NetworkClient?        with(PayClientConfigBuilder(costClass)) {            networkClient = build(config)        }        return networkClient    }}
复制代码


最终调用


val networkClient = PayNetworkClient.init(BusinessResponse::class.java) {    //配置部分    requestBean { request }    needRetry { false }    cancelOtherSession { "sendGetPayInfo" }    //回调部分,可根据需求添加subSuccess或subFailed    subSuccess { serviceSuccess(it) }    subFailed { serviceFailed(it) }}networkClient?.send()
复制代码


在定义 DSL 的过程中需要权衡冗余度、自由度、可扩展性。上面给出的伪代码消除了重复的模版代码,减少代码冗余,同时也做到自由选择配置项,有一定的自由度和可扩展性。

2.2 海外支付 SDK DSL 构建项目实践

众所周知 Android studio 中是使用 groovy 编写 gradle 脚本,而 groovy 由于是动态语言,不可避免的存在一个问题,就是代码提示不够智能,我们在使用 groovy 时往往需要配合文档进行编写;而 kotlin 是一种静态语言,使用它编写 gradle 脚本则可以有比较好的智能提示体验。


在 Gradle5.0 中,官方提供可以选择在项目中生成 Groovy 或者 kotlin DSL 构建脚本,并进一步的优化代码自动完成、重构和其他 IDE 辅助功能,为使用 Kotlin DSL 的 IDE 用户带来了极大的便利。



可见 gradle 官方也在努力将 kotlin DSL 推向大家视野中。


在我们最近的海外支付 SDK 中,采用该种方式构建项目, 部分 gradle 代码如下:


import org.jetbrains.kotlin.config.KotlinCompilerVersion
plugins { id("com.android.application") kotlin("android") kotlin("android.extensions")}
android { compileSdkVersion(28) defaultConfig { applicationId = "trip.pay.app" minSdkVersion(21) targetSdkVersion(28) versionCode = 1 versionName = "1.0" testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } }}
dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation(kotlin("stdlib-jdk8", KotlinCompilerVersion.VERSION)) implementation("com.android.support:appcompat-v7:28.0.0") implementation("com.android.support.constraint:constraint-layout:1.1.3") implementation(project(":TripPay"))}
repositories { mavenLocal() maven(url = "maven地址")}
复制代码


可以看到使用 kotlin 编写和 groovy 编写区别不大,所以即使我们要将现有工程中的 groovy 脚本重写为 kotlin 脚本,工作量也不会过大。


以上种种都表明 Kotlin DSL 相对于 groovy 的优势非常明显,那么我们是不是应该立马开始改造现有的项目?


答案是“否”,因为它目前存在一个致命的缺陷,在首次编译项目时比 groovy DSL 慢很多,大项目中这一点会被放大,所以大家在上手之前需要慎重权衡利弊。


目前我们在海外支付 SDK 中利用 kotlin DSL 构建大约在 17s,利用 groovy DSL 构建大约在 16s,时间上来说几乎没有区别,所以小型项目推荐尝试使用!


相信在不久的未来 kotlin DSL 可以解决这个问题,那么利用 kotlin DSL 构建项目势必会成为趋势。

2.3 Anko

Anko 库包括 Anko Commons、Anko Layouts、Anko SQLite、Anko Coroutines,这些都是使用 kotlin DSL 编写,这里主要介绍 Anko Layouts。


在写 Android 布局时,我们都习惯性的使用 XML 进行编写,但是可以考虑丢下冗长的 XML 写法,尝试使用 Anko Layout 来实现。


XML 写法:


<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">
<EditText android:id="@+id/todo_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/main_edit_hint" />
<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/main_button_text" />
</LinearLayout>
复制代码


Anko Layout 写法:


verticalLayout {    setGravity(Gravity.CENTER_VERTICAL)    editText {        hintResource = R.string.main_edit_hint         }.lparams(width = matchParent, height = wrapContent)    button {        textResource = R.string.main_button_text        onClick {            toast("click!")        }    }.lparams(width = matchParent, height = wrapContent)}
复制代码


实际上前文提到过,XML 本质上也是一种 DSL,但是明显使用 Anko Layout 风格更加简单、也更加灵活。


XML 编写后,我们需要 findViewById 找到控件,再对控件进行操作、赋值;Anko Layout 编写过程中,可以在布局中就直接做显示隐藏、赋值操作等,同时这种写法也有类型安全、空安全、代码复用性强的优势。


Anko Layout 由于是直接在 kt 文件中编写控件,那么它相对于 xml 来说,还有一个优势,即:减少了 XML 格式的解析过程,从而实现 CPU 资源和电量的节省。


XML 的执行流程:



Anko Layout 执行流程:



Anko 库实际上是用 kotlin 对相关类做了一层扩展包装,基于这一点,它的局限性也体现在于会增加包大小,在使用之前可以根据项目评估一下是否适合引入 Anko 库。

2.4、创建一个自己的 DSL

Kotlin DSl 的优势这么多,那么如何自定义一个 DSL?


kotlin 的扩展函数、高阶函数、lambda 表达式、中缀调用、invoke 约定和函数小括号省略等特性,使得 Kotlin 编写 DSL 尤为顺畅,我们可以使用这些特性来实现自己的“领域特定语言”。这里给一个简单的示例:


定义 Trip、Department 类


data class Trip(var name: String? = "", var address: String? = "", var departments: List<Department>? = mutableListOf(), var city: List<String>? = mutableListOf(), var culture: String? = "")
复制代码


data class Department(var name: String = "", var nameEn: String = "")
复制代码


定义中间类,主要是为了实现直接 DSL 方式添加 department 的效果


class TripBuilder {        var name: String? = ""        var address: String? = ""        var departments = mutableListOf<Department>()
fun department(block: DepartmentBuilder.() -> Unit) {// 简单的写法departments.add(DepartmentBuilder().apply(block).build())即可// 演示invoke实现 val departmentBuilder = DepartmentBuilder() block.invoke(departmentBuilder) departments.add(departmentBuilder.build()) }
fun build(): Trip = Trip(name, address, departments) }
class DepartmentBuilder { var name: String = "" var nameEn: String = ""
fun build(): Department = Department(name, nameEn)}
复制代码


创建 trip 的 DSL 写法


fun trip(block: TripBuilder.() -> Unit): Trip = TripBuilder().apply(block).build()
复制代码


实现中缀 culture 方法(只为了演示所用,实际上可以直接赋值)


infix fun Trip.culture(culture: String) {    this.culture = culture}
复制代码


最终调用效果:


val trip = trip {    name = "Trip"    address = "上海市长宁区金钟路968号凌空SOHO"    department {        name = "机票"        nameEn = "flight"    }    department {        name = "酒店"       nameEn = "hotel"    }    department {        name = "火车票"        nameEn = "train"    }}trip culture "Customer、Teamwork、Respect、Integrity、Partner"Log.i("result",trip)
复制代码


result 结果:


Trip(name=Trip, address=上海市长宁区金钟路968号凌空SOHO, departments=[Department(name=机票, nameEn=flight), Department(name=酒店, nameEn=hotel), Department(name=火车票, nameEn=train)], culture=Customer、Teamwork、Respect、Integrity、Partner)
复制代码


一个简单的 Kotlin DSL 就这样实现了,通过封装成结构化的 API 达到了直观易懂、最终调用时代码量减少的效果。即使是一个非 kotlin 开发人员也可以理解以上格式的含义,完成“Trip”对象的配置使用。

三、写在最后

1)Kotlin 编写完的 DSL 整体简洁直观,调用代码和读代码的成本都得以降低,在生产项目中可以稳定使用。


2)DSL 是通过简化语言中的元素,降低使用者的负担,使用者需要按照既定的规范进行编写。所以我们需要提供完善使用文档,以保证接入者学习成本降低。


3)在我们编写的 DSL 应用范围越来越大时,已有 DSL 往往满足不了现有的需求,我们仍然需要对 DSL 进行补充,所以在定义自己的 DSL 时需要评估后期开发维护效率,注意其可扩展性。


作者介绍


刘媛,携程金融高级开发工程师,主要负责中文版、国际版支付 Android 端的开发及维护工作。


本文转载自公众号携程技术中心(ID:ctriptech)


原文链接


https://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&mid=2697268865&idx=2&sn=8ef7147cccce782c4279e20fe1aaf765&chksm=8376f1b5b40178a330e388c0a36532131f485b767d6508f3fd5c790d014893edee4ee4bf7c96&scene=27#wechat_redirect


2019-09-24 08:003974

评论

发布
暂无评论
发现更多内容

AutoCAD 2024 Mac版如何设置自动保存文件为低版本?

魔仙苹果mac堡

CAD制图软件 AutoCAD 2024 Mac版 AutoCAD使用技巧 保存文件为低版本

tidb-loadbalance 客户端方式软负载均衡配置实践

TiDB 社区干货传送门

数据库架构设计 数据库连接

新特性解析丨TiDB 资源管控的设计思路与场景解析

TiDB 社区干货传送门

版本测评

Neuron 2.4.0发布:体验下一代工业物联网连接和管理

EMQ映云科技

UI 物联网 IoT neuron 企业号 4 月 PK 榜

微服务 - 注册中心和配置中心(Consul)

做梦都在改BUG

Java 微服务 注册中心 配置中心

超强版干货投递!Milvus 的部署心得、运维秘籍都在这里了!

Zilliz

Milvus Zilliz ChatGPT LLM zillizcloud

从零到跑通TPC-H:如何快速实现查询计划

MatrixOrigin

分布式数据库 MatrixOrigin MatrixOne TPC-H

Karmada 多云容器编排引擎支持多调度组,助力成本优化

华为云开发者联盟

云原生 后端 华为云 华为云开发者联盟 企业号 4 月 PK 榜

用数据分析的方法去做dba,维护好tidb数据库。

TiDB 社区干货传送门

6.x 实践

谷歌 Chrome 正式发布 WebGPU!Orillusion开源倒计时!

Orillusion

开源 WebGL 元宇宙 web3d #WebGPU

PCB阻焊桥存在的DFM(可制造性)问题,华秋一文告诉你

华秋电子

免费领取 | ONES 联合中国信通院发布《中国企业软件研发管理白皮书》

万事ONES

全平台数据(数据库)管理工具 DataCap 管理 Rainbond 上的所有数据库

北京好雨科技有限公司

数据库 Kubernetes 云原生 rainbond 企业号 4 月 PK 榜

苹果电脑臻选4K壁纸app:Dynamic Wallpaper

魔仙苹果mac堡

动态壁纸 Dynamic Wallpaper下载 苹果壁纸下载 mac壁纸一软件 高清壁纸下载

Mac人工智能图像降噪 Topaz Photo AI 安装激活教程

魔仙苹果mac堡

Topaz Photo AI下载 图像降噪 苹果mac软件下载 Topaz Photo AI mac Topaz Photo AI破解

HummerRisk V1.0 开发手册(微服务版)

HummerCloud

开源 微服务 云原生安全

Mysql 连接查询

WordPress 使用 TiDB Cloud 替换 MySQL

TiDB 社区干货传送门

迁移 实践案例 版本测评 应用适配

【Linux】之创建普通用户并禁止root用户远程登陆

A-刘晨阳

Linux 三周年连更 用户名

优化配置Little Snitch for Mac的规则和设置

魔仙苹果mac堡

macOS防火墙软件 Little Snitch 下载 小飞贼防火墙 Little Snitch 规则

maya软件在建模上有什么优势?

Finovy Cloud

maya 3D软件

国内外主流的8款 IT 项目管理平台

PingCode

项目管理工具 PingCode 项目研发管理软件

即时通讯技术文集(第13期):Web端即时通讯技术精华合集 [共15篇]

JackJiang

网络编程 即时通讯 IM

TiDB迁移、升级与案例分享(TiDB v4.0.11 → v6.5.1)

TiDB 社区干货传送门

迁移 版本升级 安装 & 部署 扩/缩容 6.x 实践

迁移prometheus数据

TiDB 社区干货传送门

迁移 实践案例 集群管理

迁移PD坑-cdc任务全部stop

TiDB 社区干货传送门

实践案例 集群管理 故障排查/诊断

python正则 | python小知识

AIWeker

Python python小知识 三周年连更

4 月 25 日直播预告 | 深入解读 Flink 1.17

Apache Flink

大数据 flink 实时计算

面试官:Redis有什么持久化策略?

做梦都在改BUG

Java redis 缓存 面试 持久化

阿里P8推荐学习的44个微服务架构设计模式,真的太香了!

做梦都在改BUG

Java 架构 微服务 设计模式

Parallels Desktop PD 18虚拟机关闭、停止、中止和暂停操作的区别

魔仙苹果mac堡

pd虚拟机 Parallels Desktop PD18虚拟机操作

携程Kotlin DSL开发与实践_文化 & 方法_刘媛_InfoQ精选文章