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

news/2025/3/15 6:32:34/

该设计模式适用于创建复杂对象,该复杂对象通常是由各个部分的子对象用一定的算法或者步骤构成,针对每个子对象内部算法和步骤通常是稳定的,但是该复杂对象的确实由于不同的需求而选择使用不同的子对象进行组装。对于构建该复杂的对象,通常可以使用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


http://www.ppmy.cn/news/991332.html

相关文章

MD-MTSP:成长优化算法GO求解多仓库多旅行商问题MATLAB(可更改数据集,旅行商的数量和起点)

一、成长优化算法GO 成长优化算法(Growth Optimizer,GO)由Qingke Zhang等人于2023年提出,该算法的设计灵感来源于个人在成长过程中的学习和反思机制。学习是个人通过从外部世界获取知识而成长的过程,反思是检查个体自…

阿里用户序列建模MIMN

Practice on Long Sequential User Behavior Modeling for Click-Through Rate Prediction 摘要 对于序列建模,实践了机器学习算法与在线服务协同设计的CTR预测系统,理论上可以处理无限长的用户序列。 从服务系统的角度来看,通过设计一个单…

深度学习之梯度下降算法

0.1 学习视频源于:b站:刘二大人《PyTorch深度学习实践》 0.2 本章内容为自主学习总结内容,若有错误欢迎指正! 1 线性模型 1.1 通过简单的线性模型来举例: 1.2 如图,简单的一个权重的线性模型&#xff0c…

数据结构:栈和队列的实现和图解二者相互实现

文章目录 写在前面栈什么是栈栈的实现 队列什么是队列队列的实现 用队列实现栈用栈模拟队列 写在前面 栈和队列的实现依托的是顺序表和链表,如果对顺序表和链表不清楚是很难真正理解栈和队列的 下面为顺序表和链表的实现和图解讲解 手撕图解顺序表 手撕图解单链表 …

二维深度卷积网络模型下的轴承故障诊断

1.数据集 使用凯斯西储大学轴承数据集,一共有4种负载下采集的数据,每种负载下有10种 故障状态:三种不同尺寸下的内圈故障、三种不同尺寸下的外圈故障、三种不同尺寸下的滚动体故障和一种正常状态 2.模型(二维CNN) 使…

msvcp120.dll丢失的解决方法?有什么解决方法比较推荐?

第一:介绍几种可能导致msvcp120.dll文件丢失或损坏的原因 损坏的程序安装:在安装某个程序时,可能会出现意外中断或其他错误,导致msvcp120.dll文件未能正确地被安装或被破坏。这可能是由于软件安装过程中的错误、病毒感染或硬件问题…

04mysql查询之多表查询

# 1.显示所有员工的姓名,部门号和部门名称。 SELECT last_name,e.department_id,department_name FROM employees e LEFT JOIN departments d ON e.department_id d.department_id # 2.查询90号部门员工的job_id和90号部门的location_id SELECT e.job_id,d.locat…

OAuth机制_web站点接入微软azure账号进行三方登录

文章目录 ⭐前言⭐微软三方登录流程💖 web站点获取微软账号流程💖 node封装微软登录接口💖 webapp 自定义code换token💖 调用 Microsoft Graph API💖 前端唤醒authlink进行登录回调逻辑 ⭐结束 ⭐前言 大家好&#xf…