[版权申明] 非商业目的注明出处可自由转载
博文地址: http://app.shusheng007.top/2020/10/14/1-7/
出自:shusheng007
系列文章:
秒懂设计模式之建造者模式(Builder pattern)
@[toc]
前言
构建者模式在Java世界中是一个非常实用的创建型设计模式,日常使用非常频繁。有不熟悉的小伙伴请移步到 秒懂设计模式之建造者模式(Builder pattern)进行回顾。但是当Java的挑战者Kotlin横空出世后,很多人说此设计模式失去了其用武之地,事实真的是这样吗?
你内心中是否在期待翻转剧情呢?现实往往是要打脸的,在大部分情况下,事实真的是这样!
默认参数构建对象
由于Kotlin支持默认参数和名称参数,使得Kotlin在大部分情况下不需要构建者模式就能很好的完成对象创建任务,talk is cheep, show me the code,我想必须用code来征服你们。
需求:
我们要根据不同的配置来构建一个电脑类的实例,希望可以自由配置电脑CPU、RAM、显示器、键盘以及USB端口,从而组装出不同的Computer实例。
如果使用Java来实现的话,最好的选择就是使用构建者模式,正如 秒懂设计模式之建造者模式(Builder pattern)中所展示的那样。但是如果使用Kotlin实现的话,默认参数与名称参数就可以很好的完成需求,如下代码所示:
class Computer(
val cpu: String = "英特尔",
val ram: String = "金士顿",
val usbCount: Int = 2,
val keyboard: String = "罗技",
val display: String = "京东方"
) {
override fun toString(): String {
return "Computer(cpu='$cpu', ram='$ram', usbCount=$usbCount, keyboard='$keyboard', display='$display')"
}
}
调用
val computer= Computer(ram="海力士",keyboard = "双飞燕")
如上代码所示,我们可以通过名称参数来指定修改某个参数,剩下的使用默认参数即可,不得不说确实很强大。
Kotlin中的构建者模式
那么构建者模式在Kotlin中是否真的就成为了一点用都没有的废材了呢?当然不是!
需求:
一个对象需要经过多步才能完成构建,而且希望构建的对象是不可变的,这就需要先一步一步的创造构建者,然后在最后一步使用构建者来构建对象。这么说有点抽象,让我们来举个例子。
我们的电脑需要配置CPU和RAM才是一个完整的对象,但是现在CPU与RAM不能同时到位,而是先得到CPU,然后经过很多步骤后才能得到RAM。那么我们就没有办法在得到CPU的时候就把Computer对象创建出来呢?有的同学说了,可以先把对象创建出来,然后等得到了RAM后在此对象上set进去。 这样的话那个Computer对象就成了可变对象了,会存在一些列弊端。如果通过构建者模式实现的话,我们可以多步骤构建builder,等builder准备好了,再调用其build()
方法最终创建Computer对象。
使用Java实现Builder模式相信大家已经手到擒来了,那就让我们来研究一下在Kotlin中如何实现Builder模式吧
第一版写法
其实和Java差不多,此处列出几个关键点
- Computer添加主
private
构造函数, 其属性为只读属性,供外部使用 - Computer添加第二
private
构造函数,参数为Builder
使用Builder来对Computer属性赋值 - 添加一内部类Builder,其默认是static的,相当于Java的静态内部类
Builder类的属性对外只可读,不可设置,设置通过对应的设置方法完成。注意那些设置方法使用了kotlin的apply
scope函数。 - 在Builder类中添加一个build方法,负责最终构建Computer实例
具体代码如下:
class Computer1 private constructor(
val cpu: String,
val ram: String,
val usbCount: Int,
val keyboard: String,
val display: String
) {
private constructor(builder: Builder) : this(
builder.cpu,
builder.ram,
builder.usbCount,
builder.keyboard,
builder.display
)
class Builder {
var cpu: String = ""
private set
var ram: String = ""
private set
var usbCount: Int = 0
private set
var keyboard: String = ""
private set
var display: String = ""
private set
fun setCup(inputCup: String) = apply {
this.cpu = inputCup
}
fun setRam(inputRam: String) = apply {
this.ram = inputRam
}
fun setUsb(inputUsb: Int) = apply {
this.usbCount = inputUsb
}
fun setKeyboard(inputKeyboard: String) = apply {
this.keyboard = inputKeyboard
}
fun setDisplay(inputDisplay: String) = apply {
this.display = inputDisplay
}
fun build() = Computer1(this)
}
override fun toString(): String {
return "Computer(cpu='$cpu', ram='$ram', usbCount=$usbCount, keyboard='$keyboard', display='$display')"
}
}
调用
val computer1 = Computer1.Builder().
setCup("英特尔").
setRam("金士顿").
setDisplay("三星").
setUsb(3).
setKeyboard("罗技").
build()
上面代码中所有参数均是可选的,如果某几个参数是必填的话,只要在Builder的构造函数中加入即可,如下所示
class Computer2 private constructor(... ){
...
class Builder (val cpu: String, val ram: String){
var usbCount: Int = 0
private set
var keyboard: String = ""
private set
var display: String = ""
private set
...
}
}
调用
val computer2= Computer2.Builder("英特尔","金士顿").
setDisplay("京东方").
setKeyboard("罗技").
build()
第二版写法
上面就是Builder模式在Kotlin中传统的写法。但是Kotlin有一种神奇的类型叫Kotlin Function Literals with Receiver,大概长这样:
T.() -> R
是不是有点懵逼?让我们将概念化繁为简,其直接翻译为:带接收器的函数字面量。由于Lambda是一种函数字面量,所以其可以进一步具体化为:带接收器的Lambda表达式。
例如有如下Lambda
val greet1:()->Unit= { println("hello world")}
我们可以为上面的lambda加上一个String类型的receiver,使其变成下面这样
var greet2: String.() -> Unit = { println("Hello $this") }
我们可以在{}
中以this
访问这个receiver。值得注意的是,greet2有两种等价的执行方法
greet2("world")
"world".greet2()
利用上述语法可以简化Builder写法,构建出 builder DSL的语法,使其更加紧凑。
我们在Computer类里面加一个companion object
,在里面定义一个使用Builder构建对象的方法,其入参为 接收器为Builder的Lambda表达式。
class Computer3 private constructor(...){
companion object {
inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build()
}
class Builder {
...
fun build() = Computer3(this)
}
}
这块稍微有点难度,要想理解上面的写法,首先需要理解scope函数apply
,可以通过阅读我的另一篇文章 秒懂Kotlin之轻松掌握Scope Functions (apply, also,let,run,with)来轻松掌握scope函数。
我们看一下apply
函数的定义
public inline fun T.apply(block: T.() -> Unit): T
它的入参是T.() -> Unit
返回类型是T
,此处泛型参数T
我们传入Builder类,我们就可以使用如下语法调用 inline fun build(block: Builder.() -> Unit)
方法。
调用
val computer3 = Computer3.build {
setCup("AMD")
setRam("海力士")
setDisplay("三星")
setUsb(3)
setKeyboard("双飞燕")
}
上面的写法仅仅是为Computer类提供了一种简化的调用方式,我们仍然可以使用传统调用方式来使用此构建者。
总结
在kotlin中大部分情况下是不需要Builder模式的,但是某些特殊情形下Builder模式仍然可以大放异彩。
猿猿们,又到了点赞分享的时候了,祝你们圣体健康,生活愉快...
十载求学纵苦三伏三九无悔无怨; 一朝成就再忆全心全力有苦有乐
文章评论