建造者模式
本课属于 Coddy Swift 学习旅程的“面向对象编程”单元。第 48 课,共 57 课。
构建器模式会逐步组装复杂对象。每一步设置一个部分并返回构建器,因此可以将这些步骤串联起来,最后调用 build() 创建完成的对象:
struct Request {
let url: String
let method: String
let headers: [String: String]
}
struct RequestBuilder {
private var url = ""
private var method = "GET"
private var headers: [String: String] = [:]
func url(_ u: String) -> RequestBuilder { var b = self; b.url = u; return b }
func method(_ m: String) -> RequestBuilder { var b = self; b.method = m; return b }
func header(_ k: String, _ v: String) -> RequestBuilder { var b = self; b.headers[k] = v; return b }
func build() -> Request { Request(url: url, method: method, headers: headers) }
}
let req = RequestBuilder().url("/users").method("POST").header("Auth", "token").build()
print("\(req.method) \(req.url) \(req.headers.count) header")输出:
POST /users 1 header构建器可以在创建对象之前检查整个配置,因此绝不会构建无效对象。这里,如果缺少必需部分,build() 会抛出异常:
enum BuildError: Error { case missingURL }
struct Request { let url: String; let method: String }
struct RequestBuilder {
private var url: String?
private var method = "GET"
func url(_ u: String) -> RequestBuilder { var b = self; b.url = u; return b }
func method(_ m: String) -> RequestBuilder { var b = self; b.method = m; return b }
func build() throws -> Request {
guard let url else { throw BuildError.missingURL }
return Request(url: url, method: method)
}
}
do {
_ = try RequestBuilder().method("PUT").build()
} catch {
print("cannot build: \(error)")
}输出:
cannot build: missingURL每一步都会复制结构体并修改副本,因此可以将部分配置好的构建器用作模板来创建多个对象:
struct PizzaBuilder {
private var size = "medium"
private var toppings: [String] = []
func size(_ s: String) -> PizzaBuilder { var b = self; b.size = s; return b }
func add(_ t: String) -> PizzaBuilder { var b = self; b.toppings.append(t); return b }
func build() -> String { "\(size) pizza with " + (toppings.isEmpty ? "nothing" : toppings.joined(separator: ", ")) }
}
let base = PizzaBuilder().size("large").add("cheese")
print(base.add("ham").build())
print(base.add("olives").build())输出:
large pizza with cheese, ham
large pizza with cheese, olivesSwift 的默认参数值已经可以满足简单情况(Request(url: "/", method: "GET"))。当对象有许多可选部分、各部分相互依赖,或者对象创建前需要检查规则时,使用构建器会很有帮助。
// 简单:默认参数就够了
struct Point { var x = 0, y = 0 }
// 复杂:有很多可选部分,还需要验证,可以用构建器
let report = ReportBuilder().title("Q3").add(section: s1).add(section: s2).footer("draft").build()挑战
简单为电子邮件编写一个构建器。EmailBuilder 包含可链式调用的步骤:to(_:)(可以调用多次,以添加收件人)、subject(_:)、body(_:) 和 urgent()。build() 会返回一个 Email,或者抛出 EmailError.noRecipients 或 EmailError.noSubject。紧急邮件的主题前会加上 [URGENT] 。
提供的代码会读取每封邮件的命令,各命令之间用 --- 行分隔(to ada@x.io、subject Hello、body text、urgent,然后是 send),并输出 To: ada@x.io, bo@x.io | [URGENT] Hello | text、error: no recipients 或 error: no subject。
你的代码应写在 Email.swift、EmailError.swift 和 EmailBuilder.swift 中。main.swift 包含提供的输入/输出代码,不能编辑。
亲手试试
// 提供的输入和输出代码:保持原样
var input: [String] = []
while let line = readLine() { input.append(line) }
var builder = EmailBuilder()
for line in input {
let p = line.split(separator: " ", maxSplits: 1).map(String.init)
switch p[0] {
case "to": builder = builder.to(p[1])
case "subject": builder = builder.subject(p[1])
case "body": builder = builder.body(p[1])
case "urgent": builder = builder.urgent()
case "send":
do {
let e = try builder.build()
print("To: \(e.recipients.joined(separator: ", ")) | \(e.subject) | \(e.body)")
} catch EmailError.noRecipients {
print("error: no recipients")
} catch {
print("error: no subject")
}
default: builder = EmailBuilder()
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Swift 在线编辑器