重修设计模式-创建型-建造者模式
允许用户通过链式调用方法来逐步构建复杂对象,让复杂对象的构建与它的表示分离,即对象的表示和对象的构造过程解耦。
建造者模式的原理和实现非常简单,重点在于复杂对象的构建过程和定制化。具体实现中,通常会定义一个 Builder 类,它拥有产品类的所有属性,并定义一系列链式调用的 set 方法,用于修改产品类属性,最后通过 build() 方法集中校验并创建产品类对象。
举个例子,Android 中 AlertDialog 是一个确认弹窗,其中有这样几个属性:
- title:标题,必填。
- message:展示内容,选填。
- positiveButton:确认按钮文字,选填。
- positiveButtonClick:确认按钮点击事件,选填,如果 positiveButton 有值则为必填。
- cancelable:是否可取消,选填。
通过建造者模式实现如下:
kotlin
class CAlertDialog private constructor(builder: Builder) {
private val title: String = builder.title
private val message: String? = builder.message
private val positiveButton: String? = builder.positiveButton
private val positiveButtonClick: OnClickListener? = builder.positiveButtonClick
private val cancelable: Boolean = builder.cancelable
fun show() {
println("CAlertDialog(title=$title, message=$message, positiveButton=$positiveButton, positiveButtonClick=$positiveButtonClick, cancelable=$cancelable)")
}
//定义建造者
class Builder {
var title: String = ""
var message: String? = null
var positiveButton: String? = null
var positiveButtonClick: OnClickListener? = null
var cancelable: Boolean = true
fun setTitle(title: String): Builder {
this.title = title
return this
}
fun setMessage(message: String): Builder {
this.message = message
return this
}
//使用Kotlin apply函数优化代码
fun setPositiveButton(text: String) = this.apply { this.positiveButton = text }
fun setPositiveButtonClick(click: OnClickListener) =
this.apply { this.positiveButtonClick = click }
fun setCancelable(cancelable: Boolean) = this.apply { this.cancelable = cancelable }
fun build(): CAlertDialog {
//校验依赖关系
if (positiveButton?.isNotEmpty() == true) {
if (positiveButtonClick == null) throw Throwable("请设置点击事件!")
}
return CAlertDialog(this)
}
}
}
使用时:
kotlin
val dialog = CAlertDialog.Builder()
.setTitle("标题")
.setMessage("消息")
.setPositiveButton("确定")
.setPositiveButtonClick { v -> println("确定事件") }
.setCancelable(true)
.build()
dialog.show()
可以看到调用处的代码非常简洁,可以通过链式调用配置自定义属性,并在 build 方法中校验和创建对象,还避免了对象创建中的中间状态。不过 CAlertDialog 中的属性在 Builder 又定义了一遍,造成了代码重复,这也是建造者模式的缺点。
这种方式和直接设置产品类的 set 方法差异在什么地方呢?下面通过 set 方式实现一下上面例子。
kotlin
//必填属性通过构造方法传入,就不用校验了
class CAlertDialog constructor(private var title: String) {
private var message: String? = null
private var positiveButton: String? = null
private var positiveButtonClick: OnClickListener? = null
private var cancelable: Boolean = true
fun setMessage(message: String) = this.apply { this.message = message }
fun setPositiveButton(text: String) = this.apply { this.positiveButton = text }
fun setPositiveButtonClick(click: OnClickListener) = this.apply { this.positiveButtonClick = click }
fun setCancelable(cancelable: Boolean) = this.apply { this.cancelable = cancelable }
fun show() {
println("CAlertDialog(title=$title, message=$message, positiveButton=$positiveButton, positiveButtonClick=$positiveButtonClick, cancelable=$cancelable)")
}
}
调用处:
kotlin
val dialog = CAlertDialog1("标题")
.setMessage("消息")
.setPositiveButton("确定")
.setPositiveButtonClick { v -> println("确定事件") }
.setCancelable(true)
dialog.show()
这里为产品类的一系列 set 方法也增加了返回自身,达到链式调用的目的,调用同样简洁。但实现过程中属性间依赖关系无法统一校验了,且 set 方法一定是暴露的,就意味着外部能随时修改,不能达到创建后对象不可变的目的了。
建造者模式 VS set方式:
建造者模式
:
- 优点:
- 支持链式调用
- 支持统一校验,符合早抛出,晚捕获的异常处理原则(在发现错误时,应尽早抛出异常,避免错误扩散。有助于定位问题,提高程序的健壮性)
- 支持对象创建后属性不可变
- 一次性构建对象,避免无效的中间状态
- 缺点:
- 代码重复,产品类属性需要在建造者中重新定义一遍。
- 如果产品类内部变化不大,使用建造者模式会增加复杂性。
set 方式
:
- 优点:
- 支持链式调用
- 实现简单,无重复代码
- 缺点:
- 不支持属性依赖关系的统一校验
- 必填对象过多时,构造方法中参数冗长
- 不支持不可变对象
- 有短暂的中间状态
如果不在意对象是否可变和短暂无效状态,set 方式也是可以使用的,毕竟 Builder 中的重复代码和建造者对象也会造成一些损耗,具体还是要视需求复杂程度而定,在软件编码原则和需求契合度之间做折中。
建造者与工厂模式区别:
- 建造者关注对象的创建过程,通过一系列复杂步骤创建对象,每个步骤可以独立的改变对象的状态;工厂模式关注对象的创建结果,隐藏对象的创建细节。
- 建造者侧重对象的"定制化",工厂侧重对象的"规范化"
传统建造者模式
传统建造者模式是在链式调用基础上,通过模板模式进一步封装出指挥者(Director)
、抽象建造者(Builder)
和具体建造者(ConcreteBuilder)
,以便批量的创建出相同的复杂对象,这种方式封装程度更高,更加遵循设计模式的一些原则。不过任何设计模式都不能生搬硬套,还是那句话,在享受使用设计模式所带来的便捷性的同时,也不能被其所束缚。就建造者模式而言,在我客户端开发的生涯中很少遇到传统的实现方式,更多遇到的还是上面的 Builder + 链式调用的方式,比如 OkhttpClient 、AlertDialog 等。