当前位置: 首页 > news >正文

建造者设计模式 + 高阶函数 => DSL

该设计模式适用于创建复杂对象,该复杂对象通常是由各个部分的子对象用一定的算法或者步骤构成,针对每个子对象内部算法和步骤通常是稳定的,但是该复杂对象的确实由于不同的需求而选择使用不同的子对象进行组装。对于构建该复杂的对象,通常可以使用builder设计模式。而对于kotlin语言,结合高阶函数所实现的建造者设计模式算是DSL代码分享的实践。

比如我们如果想要创建一个Server类:

class Server() {var port: Intvar address: String...
}

但是考虑到创建该对象比较复杂(该对象的成员比较多),且每一个成员的最终取值需要一定的算法策略,为了减少构造函数的参数,我们采用为该类添加一个建造者类,通过建造者类来创建该Server对象,而不是直接new该对象,为了在使用中我们直观感受到该建造者类是专门为Server类服务,故我们将该建造者类声明为该Server的内部类。

class Server(val serverBuilder: ServerBuilder) {class ServerBuilder {private var port: Int = 8080private var address: String = ""fun port(init: ServerBuilder.() -> Int) = apply { port = init() }fun address(init: ServerBuilder.() -> String) = apply { address = init() }fun build(): Server = Server(this)}
}

我们仔细观察下这个建造者类:ServerBuilder,因为我们是要该类帮我们最终构建Server类,那么我们就要求Server Builder要包含Server应该包含的所有的成员(port、address等),且这些成员都要设置成可变的,可重新被赋值的,即var。
同时为这些成员都增加一个对应的方法,方便从外部注入值。我们仔细观察下port和address方法,以port方法为例(address方法结构和port方法类似)。

port方法的参数接收一个参数,该参数是一个lambda表达式(一个高阶函数),该port的方法的返回值是apply的返回值,而我们知道kotlin的apply方法一般是作用于一个对象的,且最终的返回值就是这个对象,很明显此处的apply方法省略了this,apply的返回值就是当前的ServerBuilder对象,完成的写法应该是

this.apply {}

其等价于:

fun port(init: ServerBuilder.() -> Int): ServerBuilder {init()return this
}

apply的内部则是将lambda的表达式的返回值赋值给了该ServerBuilder对象的port成员。我们再来看下port方法所接收的lambda表达式:

ServerBuilder.() -> Int

注意看此处的ServerBuilder().是什么意思呢,在kotlin中,classname(). 常用于高阶函数中,作为高阶函数的参数。
形如:action: (Builder.() -> Int)
表示的是Function literals with receiver:这是一个方法,该方法不接收任何参数,该方法返回的是一个int,并且该方法是由Builder对象触发。

在这里插入图片描述
其实按照如上的使用的时候,Idea给我们的提示就可以看出,通过将port方法的高阶函数定义为Builder.() -> Int,就相当于我们为port方法的上下文注入下this,而该this就是当前的Builder对象。

最终使用的时候如下

val server = Server.ServerBuilder().port {8080}.address { "www.baidu.com"}

借助了apply方法我们可以实现链式调用(因为port方法和address函数返回的都是builder对象),但是我们观察这种写法还是不够DSL化,为此我们给ServerBuilder添加一个构造方法

class ServerBuilder private constructor(){private var port: Int = 8080private var address: String = ""// 此处的this(), 表示次构造器要授权给主构造器constructor(init: ServerBuilder.() -> Unit): this() { init()}fun port(init: () -> Int) = apply { port = port2() }fun address(init: ServerBuilder.() -> String) = apply { address = init() }fun build(): Server = Server(this)}
val server = Server.ServerBuilder {}.port {8080}.address {"www.baidu.com"}
也可以写成如下,把port和address写入ServerBuilder的里面,因为ServerBuilder的里面可以拿到this上线文,故最终形态:
class Server private constructor(val port: Int,val address: String,
) {private constructor(builder: ServerBuilder): this(builder.port,builder.address)class ServerBuilder private constructor(){var port: Int = 8080var address: String = ""// 此处的this(), 表示次构造器要授权给主构造器constructor(init: ServerBuilder.() -> Unit): this() { init()}fun port(init: () -> Int) = apply { port = init() }fun address(init: ServerBuilder.() -> String) = apply { address = init() }fun build(): Server = Server(this)}
}fun main() {val server = Server.ServerBuilder {port {8080}address {"www.baidu.com"}}.build()
}

为了进一步DSL化也为了向外界屏蔽ServerBuilder对象,我们可以给ServerBuilder添加静态方法

class Server private constructor(val port: Int,val address: String,
) {companion object {// inline fun build(block: ServerBuilder.() -> Unit) = Builder().apply(block).build()fun build(block: ServerBuilder.() -> Unit) = ServerBuilder {block()}.build()}private constructor(builder: ServerBuilder): this(builder.port,builder.address)class ServerBuilder private constructor(){var port: Int = 8080var address: String = ""// port方法的参数接收一个参数,该参数是一个lambda表达式(一个高阶函数),该port的方法的返回值是//apply的返回值,apply的返回值就是当前的ServerBuilder对象,完成的写法应该是this.apply// apply的内部则是将lambda的表达式的返回值赋值给了该ServerBuilder对象的port成员// 此处的this(), 表示次构造器要授权给主构造器constructor(init: ServerBuilder.() -> Unit): this() { init()}fun port(init: () -> Int) = apply { port = init() }fun address(init: ServerBuilder.() -> String) = apply { address = init() }fun build(): Server = Server(this)}
}
//测试
fun main() {val server = Server.build {port {8082}address {"www.baidu.com"}}
}

参考

https://stackoverflow.com/questions/44427382/what-does-mean-in-kotlin

相关文章:

建造者设计模式 + 高阶函数 => DSL

该设计模式适用于创建复杂对象,该复杂对象通常是由各个部分的子对象用一定的算法或者步骤构成,针对每个子对象内部算法和步骤通常是稳定的,但是该复杂对象的确实由于不同的需求而选择使用不同的子对象进行组装。对于构建该复杂的对象&#xf…...

重学C++系列之智能指针简单介绍

一、什么是智能指针 在使用堆内存时,就像使用栈内存空间一样,可以实现自释放的功能,智能指针在C库中也是类模板之一。 二、智能指针有几种 有四种。auto_ptr, unique_ptr, shared_ptr, weak_ptr 其中后三个是C11支持,第一个已经被…...

LabVIEW开发航天器动力学与控制仿真系统

LabVIEW开发航天器动力学与控制仿真系统 计算机仿真是工程设计和验证的非常有用的工具。它节省了大量的时间、金钱和精力。航天器动力学与控制仿真系统由LabVIEW程序开发,它是模拟航天器等动态系统的有用工具。还可轻松与硬件连接并输出真实信号。 项目采用系统工…...

享元模式——实现对象的复用

1、简介 1.1、概述 当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。例如,在一个文本字符串中存在很多重复的字符,如果每个字符都用一个单独的对象来表示,将会占用较多的内存…...

【GreenDao】关联表实现,父表关联多个子表

要在GreenDao中实现温湿度采集器表和采集数据表的关联,并在删除温湿度表时同时删除对应的采集数据,可以按照以下步骤进行操作: 在GreenDao的实体类中定义温湿度采集器表(Parent Table)和采集数据表(Child …...

python网站创建005:数据交互

目标&#xff1a;本章讲解不同控件下&#xff0c; 数据在前端和后端之间的交互 控件有&#xff1a; 输入框 密码输入框 单选框 多选框 下拉框 多行文本框 不同控件中如何将数据传入后端&#xff1f;请看一下html代码 <!DOCTYPE html> <html> <head><meta …...

golang 字符串操作、处理

一、golang的字符串长度 1. len()内置系统函数&#xff0c;计算字符串结果是字符串的字节长度&#xff0c;不是字符长度 //1.ASCII字符串长度&#xff08;字节长度&#xff09; str1 : "wo ai zhong guo" fmt.Println(len(str1)) //15//2.带中文的字符串长度&…...

Nginx配置WebSocket反向代理

1、WebSocket协议 ​ WebSocket协议相比较于HTTP协议成功握手后可以多次进行通讯&#xff0c;直到连接被关闭。但是WebSocket中的握手和HTTP中的握手兼容&#xff0c;它使用HTTP中的Upgrade协议头将连接从HTTP升级到WebSocket。这使得WebSocket程序可以更容易的使用现已存在的…...

devops(后端)

1.前言 该devpos架构为gitlabjenkinsharbork8s&#xff0c;项目是java项目&#xff0c;流程为从gitlab拉取项目代码到jenkins&#xff0c;jenkins通过maven将项目代码打成jar包&#xff0c;通过dockerfile构建jdk环境的镜像并把jar包放到镜像中启动&#xff0c;构建好的镜像通…...

Ubuntu安装企业微信

Ubuntu安装企业微信_ubuntu下安装企业微信_星光2020的博客-CSDN博客 在Ubuntu环境安装企业微信可以参考 https://github.com/zq1997/deepin-wine 所述的方法 首先运行 $ wget -O- https://deepin-wine.i-m.dev/setup.sh | sh 然后就可以像安装其它软件一样安装企业微信或其它…...

Prometheus 的应用服务发现及黑河部署等

目录 promtool检查语法 部署Prometheus Server 检查语法是否规范 部署node-exporter 部署Consul 直接请求API进行服务注册 使用register命令注册服务&#xff08;建议使用&#xff09; 单个和多个注册&#xff0c;多个后面多加了s 在Prometheus上做consul的服务发现 部署…...

JAVA SE -- 第十二天

&#xff08;全部来自“韩顺平教育”&#xff09; 常用类 一、包装类 1、包装类的分类 ①针对八种基本数据类型相应的引用类型--包装类 ②有了类的特点&#xff0c;就可以调用类中的方法 ③ 基本数据类型包装类booleanBooleancharCharacterbyteByteshortShortintInteger…...

实战:工作中对并发问题的处理

大家好&#xff0c;我是 方圆。最近在接口联调时发生了数据并发修改问题&#xff0c;我想把这个问题讲解一下&#xff0c;并把当时提出的解决方案进行实现&#xff0c;希望它能在大家以后在遇到同样的问题时提供一些借鉴和思考的方向。原文还是收录在我的 Github: enthusiasm 中…...

腾讯云Cloud Studio:基于Claude快速完成Excel工资自动核算

目录 1 什么是Cloud Studio&#xff1f;2 注册与代码管理2.1 账号注册2.2 Git关联 3 实战&#xff1a;Excel工资自动核算3.1 创建项目与配置3.2 “念咒师”Claude GPT3.3 代码编写与运行 1 什么是Cloud Studio&#xff1f; Cloud Studio是腾讯云为开发者提供的一个基于浏览器的…...

Spring Boot OAuth2 快速入门示例

系统要求 Spring Authorization Server 需要JDK1.8及以上版本。 项目搭建 使用在线项目初始化器 https://start.spring.io/ 生成项目[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ljKbMI4H-1690726855433)(images/screenshot_1690602511482.png)…...

MethodInterceptor

目录 1 MethodInterceptor 1.1 HandleSync 1.2 HandleException 1.3 /// This will be called via Reflection MethodInterceptor HandleSync private void HandleSync(IReadOnlyList<MethodFilterAttribute> filterAttributes, IReadOnlyList<ExceptionFilte…...

PID模块化__以stm32直流电机速度为例

文章目录 前言一、相关PID源码.c.h 二、如何使用1.创建变量2.初始化3.运算4.修改pid参数 总结 前言 本篇使用到的基于这个STM32CubeMX 直流电机PID速度控制、HAL库、cubemx、PID、速度控制、增量式 由于上次使用的pid没有模块化&#xff0c;当多出使用pid的时候就会很麻烦 所以…...

Java ~ Collection/Executor ~ DelayQueue【总结】

前言 文章 相关系列&#xff1a;《Java ~ Collection【目录】》&#xff08;持续更新&#xff09;相关系列&#xff1a;《Java ~ Executor【目录】》&#xff08;持续更新&#xff09;相关系列&#xff1a;《Java ~ Collection/Executor ~ DelayQueue【源码】》&#xff08;学…...

前端高级面试题-安全相关

1 XSS 跨⽹站指令码&#xff08;英语&#xff1a; Cross-site scripting &#xff0c;通常简称为&#xff1a; XSS &#xff09;是⼀种⽹站应⽤程式的安全漏洞攻击&#xff0c;是代码注⼊的⼀种。 它允许恶意使⽤者将程式码注⼊到⽹⻚上&#xff0c;其他使⽤者在观看⽹⻚时就会…...

【前缀和】560.和为 K 的子数组

Halo&#xff0c;这里是Ppeua。平时主要更新C&#xff0c;数据结构算法&#xff0c;Linux与ROS…感兴趣就关注我bua&#xff01; 和为K的子数组 题目:示例:题解&#xff1a;解法一:解法二: 题目: 示例: 题解&#xff1a; 解法一: 暴力解法:我们很容易想到通过两个for循环去遍…...

Hasklig字体终极指南:多语言编程环境中的完美适配方案

Hasklig字体终极指南&#xff1a;多语言编程环境中的完美适配方案 【免费下载链接】Hasklig Hasklig - a code font with monospaced ligatures 项目地址: https://gitcode.com/gh_mirrors/ha/Hasklig Hasklig是一款专为程序员设计的等宽字体&#xff0c;通过智能连字技…...

JDK 25模块路径在统信UOS/麒麟V10上启动失败?3步定位osgi-container冲突、jni.so符号缺失与jmod签名验签绕过方案

第一章&#xff1a;Java 25 模块化部署国产化适配方案Java 25 引入了更严格的模块系统约束与原生镜像增强能力&#xff0c;为在麒麟V10、统信UOS、openEuler等国产操作系统上实现轻量、安全、可验证的Java应用部署提供了新路径。适配核心聚焦于JVM层兼容性、模块图裁剪、国密算…...

CST中利用SPICE语言自定义复杂lumped element电路的实战指南

1. 突破CST自带元件的限制&#xff1a;为什么需要SPICE语言 刚开始用CST做电路仿真时&#xff0c;我也觉得自带的RLC元件够用了——直到遇到一个带滤波功能的耦合器项目。当时需要模拟一个包含寄生参数的复杂匹配网络&#xff0c;自带的并联RLC元件死活调不出理想的频响曲线。这…...

Z-Image-GGUF开源模型价值:Z-Image原始论文复现支持+GGUF量化技术白皮书同步发布

Z-Image-GGUF开源模型价值&#xff1a;Z-Image原始论文复现支持GGUF量化技术白皮书同步发布 1. 项目核心价值&#xff1a;一次部署&#xff0c;双重收获 如果你正在寻找一个既能体验前沿文生图模型&#xff0c;又能深入了解其底层技术原理的解决方案&#xff0c;那么Z-Image-…...

两端间隔数总个数

两端间隔数总个数 结尾序号 - 开头序号 1需要将索引还原成长度&#xff0c;索引1就好了...

手把手教你为本地LLM(Llama/Qwen)实现打字机式流式输出,Gradio+Transformers保姆级教程

手把手教你为本地LLM实现打字机式流式输出&#xff1a;Gradio与Transformers深度整合指南 当我们在本地部署大语言模型时&#xff0c;最令人沮丧的体验莫过于盯着进度条等待完整响应。想象一下这样的场景&#xff1a;你向模型提出一个复杂问题&#xff0c;屏幕陷入长达十几秒的…...

网络电台个性化高效管理:foobox-cn技术实现与应用指南

网络电台个性化高效管理&#xff1a;foobox-cn技术实现与应用指南 【免费下载链接】foobox-cn DUI 配置 for foobar2000 项目地址: https://gitcode.com/GitHub_Trending/fo/foobox-cn foobox-cn作为foobar2000的DUI配置方案&#xff0c;通过创新的电台管理系统架构&…...

3个变革性步骤:用163MusicLyrics彻底解决歌词获取难题

3个变革性步骤&#xff1a;用163MusicLyrics彻底解决歌词获取难题 【免费下载链接】163MusicLyrics Windows 云音乐歌词获取【网易云、QQ音乐】 项目地址: https://gitcode.com/GitHub_Trending/16/163MusicLyrics 在数字化音乐时代&#xff0c;歌词已不再是简单的文字附…...

CVAT:让计算机视觉标注效率提升80%的开源数据引擎

CVAT&#xff1a;让计算机视觉标注效率提升80%的开源数据引擎 【免费下载链接】cvat Annotate better with CVAT, the industry-leading data engine for machine learning. Used and trusted by teams at any scale, for data of any scale. 项目地址: https://gitcode.com/…...

LoRA训练助手效果展示:动漫风格迁移作品集

LoRA训练助手效果展示&#xff1a;动漫风格迁移作品集 1. 引言 你是否曾经想过&#xff0c;把自己拍摄的普通照片转换成新海诚风格的唯美画面&#xff0c;或者让日常场景拥有吉卜力工作室的梦幻质感&#xff1f;现在&#xff0c;这一切都不再是梦想。通过LoRA训练助手&#x…...