Wzorzec budowniczy
Część sekcji Programowanie obiektowe ścieżki Swift w Coddy. Lekcja 48 z 57.
Wzorzec buildera tworzy złożony obiekt krok po kroku. Każdy krok ustawia jedną jego część i zwraca buildera, dzięki czemu kroki można łączyć, a końcowe wywołanie build() tworzy gotowy obiekt:
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")Wynik:
POST /users 1 headerKonstruktor może sprawdzić całą konfigurację przed utworzeniem obiektu, dzięki czemu nie zostanie utworzony nieprawidłowy obiekt. Tutaj build() zgłasza błąd, gdy brakuje wymaganego elementu:
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)")
}Wynik:
cannot build: missingURLKażdy krok kopiuje strukturę i zmienia kopię, więc częściowo skonfigurowany konstruktor można ponownie wykorzystać jako szablon dla kilku obiektów:
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())Wynik:
large pizza with cheese, ham
large pizza with cheese, olivesDomyślne wartości parametrów w Swift już wystarczają w prostych przypadkach (Request(url: "/", method: "GET")). Konstruktor przydaje się, gdy obiekt ma wiele opcjonalnych części, części zależne od siebie lub reguły, które trzeba sprawdzić, zanim będzie mógł istnieć.
// proste: argumenty domyślne wystarczą
struct Point { var x = 0, y = 0 }
// złożone: wiele opcjonalnych części i walidacja -> builder
let report = ReportBuilder().title("Q3").add(section: s1).add(section: s2).footer("draft").build()Wyzwanie
ŁatwyNapisz konstruktor wiadomości e-mail. EmailBuilder udostępnia łańcuchowe kroki to(_:) (można wywołać kilka razy, dodając odbiorców), subject(_:), body(_:) i urgent(). build() zwraca Email albo zgłasza EmailError.noRecipients lub EmailError.noSubject. Pilna wiadomość e-mail ma przed tematem dodany tekst [URGENT] .
Dostarczony kod odczytuje polecenia dla każdej wiadomości e-mail, rozdzielone wierszami --- (to ada@x.io, subject Hello, body text, urgent, a następnie send), i wyświetla To: ada@x.io, bo@x.io | [URGENT] Hello | text, error: no recipients lub error: no subject.
Twój kod umieść w plikach Email.swift, EmailError.swift i EmailBuilder.swift. Plik main.swift zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.
Spróbuj swoich sił
// Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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()
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Klasy i obiekty
Praca z plikamiDefiniowanie klasMetody i selfInicjalizatoryKlasy są referencjamiPowtórzenie — karta biblioteczna4Hermetyzacja
Kontrola dostępuTylko do odczytu z zewnątrzOchrona stanuInicjalizatory, które mogą zawieśćPodsumowanie — konto bankowe7Polimorfizm i typy generyczne
PolimorfizmRzutowanie typówFunkcje generyczneTypy generycznePodsumowanie – kalkulator kształtów2Struktury i typy wartościowe
StrukturySemantyka wartościMetody modyfikująceStruktura czy klasaPodsumowanie – element inwentarza5Dziedziczenie
PodklasyPrzesłanianie metodInicjalizatory i superfinal i hierarchiePodsumowanie — hierarchia pracowników8Standardowe protokoły
CustomStringConvertibleEquatableComparableHashablePodsumowanie — własne sortowanie11Projekt: Zarządzanie biblioteką
Książki i czytelnicyWypożyczanie książekPoćwicz samodzielnie: Edytor online Swift