Builder pattern 在 iOS 中的实践

和其它设计模式一样,建造者模式的目的是为了减少可变状态的需求,使得结果是可预测的。因为建造者模式的是由纯粹的输入和输出构建起来的,因此也就更加易于测试和调试。

构建对象

建造者模式的核心就是使用一个专门的建造者类型来构建对象。比如说,要创建一个图标加文本的按钮,常见的写法如下:

1
2
3
let button = UIButton()
button.setimage(UIImage(named: "icon"), for: .normal)
button.setTitle("Hello", for: .normal)

而如果采用建造者模式的话,代码就会变成如下这样:

1
2
3
4
let button = ButtonBuilder()
.title("Hello")
.image(UIImage(named: "icon"))
.build()

由上面的代码可以看出,通过 ButtonBuilder 调用一系列的链式方法调用设置所有的属性,并且在最后调用 build 方法创建按钮实例。每次的链式调用都会返回 ButtonBuilder 本身,这样就会更容易地在不引入另外的本地变量的情况下实现链式调用。

建造者模式的优点

建造者模式有两个主要的优点。

1. 隐藏属性

因为引入了 builder 类型,因此可以将原本的对象的部分属性和方法隐藏起来使外部无法直接访问。这样可以让视图变得更简洁,也不需要针对内容的变化产生响应。

2. 阻止状态共享

建造者模式可以防止共享可变状态。这是什么意思呢?举个例子,如果需要将用户的输入转换为富文本显示在屏幕上,常见的做法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class TextViewController: UIViewController {
private let text: NSMutableAttributedString

private func appendString(_ string: String) {
let attrString = NSMutableAttributedString(string: string, attributes: textAttributes)

text.append(attrString)
}

private func drawing() {
// 将text绘制到屏幕上
}
}

上面的代码看上去没有问题,但是,如果在绘制的时候,text 发生了改变,则最终显示在屏幕上的内容与预期的并不一致,这便是可变状态共享可能造成的问题。
如果使用建造者模式,那么状态共享的问题便可以得到解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class AttributedStringBuilder {
typealias Attributes = [NSAttributedStringKey : Any]

private let text = NSMutableAttributedString(string: "")

@discardableResult
func append(_ string: String, attributes: Attributes) -> AttributedStringBuilder {
let attrString = NSAttributedString(string: string, attributes: attributes)
text.append(attrString)
return self
}

func build() -> NSAttributedString {
return NSAttributedString(attributedString: text)
}
}

class TextViewController: UIViewController {
private let textBuilder = AttributedStringBuilder()

private func appendString(_ string: String) {
textBuilder.append(string, attributes: textAttributes)
}

private func drawing() {
// 将text绘制到屏幕上
let text = textBuilder.build()
}
}

在引入建造者模式之后,由于在绘制的时候才会生成最后的富文本,用户改变的内容将不会影响到已经生成的富文本,因此也成功地阻止了可变状态的共享所产生的负面影响。

屏蔽复杂度

建造者模式也可以为复杂任务提供简单的API。按照个人的理解,这与系统提供的高阶函数如mapfilter等非常类似,就是将所有的操作通过链式调用组合起来。在此处便不再赘述了。