Pattern Builder
Fa parte della sezione Programmazione orientata agli oggetti del percorso Swift di Coddy. Lezione 48 di 57.
Il pattern builder assembla un oggetto complesso un passaggio alla volta. Ogni passaggio imposta una parte e restituisce il builder, così i passaggi possono essere concatenati, e un build() finale crea l'oggetto completo:
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")Output:
POST /users 1 headerIl builder può controllare l’intera configurazione prima di creare l’oggetto, così non viene mai costruito un oggetto non valido. Qui build() genera un errore quando manca una parte obbligatoria:
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)")
}Output:
cannot build: missingURLOgni passaggio copia la struct e modifica la copia, così un builder configurato in parte può essere riutilizzato come modello per diversi oggetti:
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())Output:
large pizza with cheese, ham
large pizza with cheese, olivesI valori predefiniti dei parametri di Swift coprono già i casi semplici (Request(url: "/", method: "GET")). Un builder è utile quando un oggetto ha molte parti opzionali, parti che dipendono l’una dall’altra o regole da verificare prima che possa esistere.
// semplice: gli argomenti predefiniti sono sufficienti
struct Point { var x = 0, y = 0 }
// complesso: molte parti opzionali e validazione -> builder
let report = ReportBuilder().title("Q3").add(section: s1).add(section: s2).footer("draft").build()Sfida
FacileScrivi un builder per i messaggi email. EmailBuilder ha passaggi concatenabili to(_:) (può essere chiamato più volte, aggiungendo destinatari), subject(_:), body(_:) e urgent(). build() restituisce un Email oppure genera EmailError.noRecipients o EmailError.noSubject. Un'email urgente ha [URGENT] davanti all'oggetto.
Il codice fornito legge i comandi per ogni email, separati da righe --- (to ada@x.io, subject Hello, body text, urgent, poi send) e stampa To: ada@x.io, bo@x.io | [URGENT] Hello | text, error: no recipients oppure error: no subject.
Il tuo codice va inserito in Email.swift, EmailError.swift e EmailBuilder.swift. main.swift contiene il codice di input/output fornito e non può essere modificato.
Provalo tu
// Codice di input/output fornito: lascialo così com'è
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()
}
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Classi e oggetti
Lavorare con i fileDefinire le classiMetodi e selfInizializzatoriLe classi sono riferimentiRiepilogo - Tessera della biblioteca4Incapsulamento
Controllo degli accessiSola lettura dall’esternoProteggere lo statoInizializzatori che possono fallireRiepilogo - Conto bancario7Polimorfismo e tipi generici
PolimorfismoCasting dei tipiFunzioni genericheTipi genericiRipasso - Calcolatore di forme2Struct e tipi valore
Le structSemantica dei valoriMetodi mutantiStruct o classeRiepilogo - Articolo di inventario5Ereditarietà
SottoclassiSovrascrivere i metodiInizializzatori e superfinal e gerarchieRiepilogo - Gerarchia dei dipendenti8Protocolli standard
CustomStringConvertibleEquatableComparableHashableRiepilogo - Ordinamento personalizzato11Progetto: Gestione della biblioteca
Libri e utentiPrestito dei libriEsercitati da solo: Playground Swift