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

news2024/9/21 2:40:54

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

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

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

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

class Server(val serverBuilder: ServerBuilder) {
    class ServerBuilder {
        private var port: Int = 8080
        private 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 = 8080
        private 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 = 8080
        var 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 = 8080
        var 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.coloradmin.cn/o/812063.html

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

相关文章

Vue2 第六节 key的作用与原理

(1)虚拟DOM (2)v-for中的key的作用 一.虚拟DOM 1.虚拟DOM就是内存中的数据 2.原生的JS没有虚拟DOM: 如果新的数据和原来的数据有重复数据,不会在原来的基础上新加数据,而是重新生成一份 3. Vue会有虚拟…

结构方程模型的绘制

模型的绘制是我们最终呈现出的一个结果 所以说这个课程主要关注的有两点 第一点就是模型图的绘制 第二点就是结果的解释 关于中间计算过程和背后的理论的一个结果 在本文章的所有的讲解过程中 只注重模型图的绘制方法 如何高效的绘制出所需要的一个模型图 同时能够调整…

【小吉带你学Git】Git分支

🎊专栏【Git】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 🎆音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题🥰 文章目录 🤖概述🍔什么是分支🍔使用分支的好处&am…

百万QPS系统如何设计?

一、关系链业务简介 从主站业务角度来看,关系链指的是用户A与用户B的关注关系。以关注属性细分,以关注(订阅)为主,还涉及拉黑、悄悄关注、互相关注、特别关注等多种属性或状态。目前主站关系链量级较大,且还…

九、HAL_IWDG独立看门狗的使用

1、开发环境 (1)Keil MDK: V5.38.0.0 (2)STM32CubeMX: V6.8.1 (3)MCU: STM32F407ZGT6 2、IWDG简介 (1)IWDG即独立看门狗。 (2)看门狗本质上是一个定时器,设置一个时间,时间到即让程序复位。所以需要在在时间未到之前重置定时器,也就是喂…

线性表详细讲解

2.1 线性表的定义和特点2.2 案例引入2.3 线程表的类型定义2.4 线性表的顺序表示和实现2.4.1 线性表的顺序存储表示2.4.2 线性表的结构类型定义2.4.3 顺序表基本操作的实现2.4.4 顺序表总结 2.5 线性表的链式表示和实现2.5.1 线性表的链式存储表示2.5.2 单链表的实现&#xff08…

ARM裸机-3

1、嵌入式和单片机的区别 1.1、芯片平台 主流的单片机平台:51、PIC、STM32、AVR、MSP430等 主流的嵌入式平台:ARM、PPC、MIPS 1.2、资源、价格、应用领域 单片机片上资源有限、价格低、应用领域多为小家电、终端设备等。 嵌入式系统片上资源丰富、价格…

数据库连接及使用Statement对象完成CRUD

一、数据库连接: 二、使用Statement对象完成CRUD: 1、插入: 2、删除 3、修改 4、查询 三、ORM对象关系映射

数据结构:顺序表详解

数据结构:顺序表详解 一、 线性表二、 顺序表概念及结构1. 静态顺序表:使用定长数组存储元素。2. 动态顺序表:使用动态开辟的数组存储。三、接口实现1. 创建2. 初始化3. 扩容4. 打印5. 销毁6. 尾插7. 尾删8. 头插9. 头删10. 插入任意位置数据…

pytorch 中 view 和reshape的区别

在 PyTorch(一个流行的深度学习框架)中, reshape 和 view 都是用于改变张量(tensor)形状的方法,但它们在实现方式和使用上有一些区别。下面是它们之间的主要区别: 实现方式: reshap…

13年测试经验,性能测试-压力测试指标分析总结,看这篇就够了...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 一般推荐&#xf…

Jmeter环境变量配置及测试

上图是Windows版本的测试结果。 Windows系统: win11:“此电脑”——鼠标右键“属性”——“高级系统设置”——“环境变量” 1.1 新建“系统变量”:JMETER_HOME JMETER_HOME变量值为解压后的jmeter路径,如: D:\apach…

AD21原理图的高级应用(三)原理图多通道的应用

(三)原理图多通道的应用 在很多大型的设计过程中,我们可能会遇到需要重复使用某个图纸,如果使用常规的复制粘贴,虽然可以达到设计要求,但原理图的数量将会变得庞大而烦琐。Altium Designer 支持多通道设计。 多通道设…

数字图像处理(番外)图像增强

图像增强 图像增强的方法是通过一定手段对原图像附加一些信息或变换数据,有选择地突出图像中感兴趣的特征或者抑制(掩盖)图像中某些不需要的特征,使图像与视觉响应特性相匹配。 图像对比度 图像对比度计算方式如下: C ∑ δ δ ( i , j …

数学学习——最优化问题引入、凸集、凸函数、凸优化、梯度、Jacobi矩阵、Hessian矩阵

文章目录 最优化问题引入凸集凸函数凸优化梯度Jacobi矩阵Hessian矩阵 最优化问题引入 例如:有一根绳子,长度一定的情况下,需要如何围成一个面积最大的图像?这就是一个最优化的问题。就是我们高中数学中最常见的最值问题。 最优化…

【C++进阶:哈希--unordered系列的容器及封装】

本课涉及到的所有代码都见以下链接,欢迎参考指正! practice: 课程代码练习 - Gitee.comhttps://gitee.com/ace-zhe/practice/tree/master/Hash unordered系列关联式容器 在C98中,STL提供了底层为红黑树结构的一系列关联式容器,在…

React井字棋游戏官方示例

在本篇技术博客中,我们将介绍一个React官方示例:井字棋游戏。我们将逐步讲解代码实现,包括游戏的组件结构、状态管理、胜者判定以及历史记录功能。让我们一起开始吧! 项目概览 在这个井字棋游戏中,我们有以下组件&am…

交叉编译工具链的安装、配置、使用

一、交叉编译的概念 交叉编译是在一个平台上生成另一个平台上的可执行代码。 编译:一个平台上生成在该平台上的可执行文件。 例如:我们的Windows上面编写的C51代码,并编译成可执行的代码,如xx.hex.在C51上面运行。 我们在Ubunt…

jellyfin搭建服务器后,快解析端口映射让外网访问

Jellyfin是一款相对知名的影音服务器,是一套多媒体应用程序软件套装,可以有效的组织管理和共享数字媒体文件,不少伙伴喜欢用jellyin在本地自己主机上搭建自己的服务器。当本地搭建服务器后,面对动态IP和无公网IP环境困境下&#x…

【javaSE】面向对象程序三大特性之封装

目录 封装的概念 访问限定符 说明 访问private所修饰的变量的方法 封装扩展之包 包的概念 导入包中的类 注意事项 自定义包 基本规则 操作步骤 步骤一 ​编辑步骤二 ​编辑 步骤三 步骤四 步骤五 包的访问权限控制举例 常见的包 static成员 再谈学生类 s…