虔诚地向Builder模式道个歉
引子
曾经在博文中发表过一些关于“Builder模式”的错误言论,误导了读者。今天在此虔诚地向Builder模式道个歉。
Kotlin 相较于 Java 的最大优势就是降低复杂度。在库接口设计及内部实现时就要充分发挥 Kotlin 的优势,比如 Kotlin 的世界里已不需要 Builder 模式了。
Builder 模式有如下优势:
- 为参数标注语义:在Builder模式中,每个属性的赋值都是一个函数,函数名标注了属性语义。
- 可选参数&分批赋值:Builder模式中,除了必选参数,其他参数是可选的,可分批赋值。而直接使用构造函数必须一下子为所有参数赋值。
- 增加参数约束条件:可以在参数不符合要求时,抛出异常。
但 Builder 模式也有代价,新增了一个中间类
Builder
。使用 Kotlin 的
命名参数
+参数默认值
+require()
语法,在没有任何副作用的情况下就能实现 Builder 模式:class Person( val name: String, //'为以下可选参数设置默认值' val gender: Int = 1, val age: Int= 0, val height: Int = 0, val weight: Int = 0 ) //'使用命名参数构建 Person 实例' val p = Person(name = “taylor”,gender = 1,weight = 43)
命名参数为每个实参赋予了语义,而且不需要按构造方法中声明的顺序来赋值,可以跳着来。
如果想增加参数约束条件可以调用
require()
方法:data class Person( // 这个是必选参数 val name: String, val gender: Int = 1, val age: Int= 0, val height: Int = 0, val weight: Int = 0 ){ //'在构造函数被调用的时候执行参数合法检查' init { require(name.isNotEmpty()){”name cant be empty“} } }
此时如果像下面这样构造 Person,则会抛出异常:
val p = Person(name="",gender = 1) java.lang.IllegalArgumentException: name cant be empty
本来在 build() 方法中执行的额外初始化逻辑也可以全部写在
init
代码块中。
上述结论好像在给定的场景中找不出任何毛病。
但我遗漏了一个场景,“构建对象”和“属性赋值”是可以分开进行的。比如下面这个场景。
syntax = "proto3";
message Event {
int64 time = 1; // 事件生成时间
string id = 2; // 事件唯一标识符
string name = 3;// 事件名称
}
message BatchEvent {
repeated Event event = 1;
}
使用 protobuf 定义了两个结构体,其中Event
表示单个事件,而BatchEvent
表示批量事件,它持有多个事件。
protobuf对应的java代码会使用建造者模式,这为构建批量事件提供了便利。
假设有一个事件流:
val initEvent = event {
time = System.currentTimeMillis()
id = UUID.randomUUID()
name = "init"
}
val eventFlow = MutableStateFlow(initEvent)
不同类型的事件在 eventFlow 中流动。当遇到 flush 事件时,会将之前堆积的所有事件包装成 BatchEvent 上传网络。
这就是一个属性赋值和构建对象分开进行的场景,即一直为 BatchEvent 对象的 event 属性追加事件,直到遇到了特殊事件时才构建对象:
var builder = BatchEvent.newBuilder()
eventFlow.collect { event ->
builder.addEvent(event)
if(event.name == "flush") {
upload(builder.build())
builder = BatchEvent.newBuilder()
}
}
最后再下一个对 Builder 模式完整的总结:
- 为参数标注语义:在Builder模式中,每个属性的赋值都是一个函数,函数名标注了属性语义。
- 可选参数&分批赋值:Builder模式中,除了必选参数,其他参数是可选的,可分批赋值。而直接使用构造函数必须一下子为所有参数赋值。
- 增加参数约束条件:可以在参数不符合要求时,抛出异常。
- 方便实现属性赋值和构造对象的分离。
转载自:https://juejin.cn/post/7249904542866047031