תבנית הבנאי
חלק מהיחידה תכנות מונחה עצמים במסלול ה-Swift של Coddy. שיעור 48 מתוך 57.
תבנית הbuilder מרכיבה אובייקט מורכב שלב אחר שלב. כל שלב מגדיר חלק אחד ומחזיר את הבנאי, כך שאפשר לשרשר את השלבים, וקריאה סופית ל־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, olivesערכי ברירת המחדל של פרמטרים ב-Swift כבר מכסים מקרים פשוטים (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 אונליין