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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值