Erstellermuster
Teil des Abschnitts Objektorientierte Programmierung der Swift-Journey von Coddy. Lektion 48 von 57.
Das Builder-Muster setzt ein komplexes Objekt Schritt für Schritt zusammen. Jeder Schritt legt einen Teil fest und gibt den Builder zurück, sodass die Schritte verkettet werden können, und ein abschließendes build() erstellt das fertige Objekt:
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")Ausgabe:
POST /users 1 headerDer Builder kann die gesamte Konfiguration überprüfen, bevor er das Objekt erstellt, sodass niemals ein ungültiges Objekt erstellt wird. Hier löst build() einen Fehler aus, wenn ein erforderlicher Bestandteil fehlt:
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)")
}Ausgabe:
cannot build: missingURLJeder Schritt kopiert die Struktur und ändert die Kopie, sodass ein teilweise konfigurierter Builder als Vorlage für mehrere Objekte wiederverwendet werden kann:
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())Ausgabe:
large pizza with cheese, ham
large pizza with cheese, olivesSwifts Standardparameterwerte decken einfache Fälle bereits ab (Request(url: "/", method: "GET")). Ein Builder lohnt sich, wenn ein Objekt viele optionale Bestandteile hat, Bestandteile voneinander abhängen oder Regeln geprüft werden müssen, bevor es erstellt werden kann.
// einfach: Standardargumente reichen aus
struct Point { var x = 0, y = 0 }
// komplex: viele optionale Teile und Validierung -> Builder
let report = ReportBuilder().title("Q3").add(section: s1).add(section: s2).footer("draft").build()Aufgabe
EinfachSchreibe einen Builder für E-Mail-Nachrichten. EmailBuilder verfügt über verkettbare Schritte to(_:) (kann mehrmals aufgerufen werden und fügt Empfänger hinzu), subject(_:), body(_:) und urgent(). build() gibt ein Email zurück oder wirft EmailError.noRecipients beziehungsweise EmailError.noSubject. Einer dringenden E-Mail wird [URGENT] vor dem Betreff vorangestellt.
Der bereitgestellte Code liest Befehle für jede E-Mail, die durch Zeilen mit --- getrennt sind (to ada@x.io, subject Hello, body text, urgent, danach send), und gibt To: ada@x.io, bo@x.io | [URGENT] Hello | text, error: no recipients oder error: no subject aus.
Dein Code gehört in Email.swift, EmailError.swift und EmailBuilder.swift. main.swift enthält den bereitgestellten Eingabe-/Ausgabecode und kann nicht bearbeitet werden.
Probier es selbst
// Bereitgestellter Ein-/Ausgabe-Code: belasse ihn wie er ist
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()
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Klassen und Objekte
Arbeiten mit DateienKlassen definierenMethoden und selfInitialisiererKlassen sind ReferenzenRückblick – Bibliotheksausweis4Kapselung
ZugriffskontrolleVon außen schreibgeschütztZustand schützenFehlschlagende InitialisiererRückblick – Bankkonto7Polymorphismus und Generika
PolymorphismusTypumwandlungGenerische FunktionenGenerische TypenZusammenfassung – Shape-Rechner2Strukturen und Werttypen
StrukturenWertsemantikVerändernde MethodenStruktur oder KlasseZusammenfassung – Inventarelement5Vererbung
UnterklassenMethoden überschreibenInitialisierer und superfinal und HierarchienRückblick – Mitarbeiterhierarchie8Standardprotokolle
CustomStringConvertibleEquatableComparableHashableRückblick – Benutzerdefiniertes Sortieren11Projekt: Bibliotheksverwaltung
Bücher und MitgliederBücher ausleihenÜbe selbstständig: Swift-Playground