Patron Builder
Fait partie de la section Programmation orientée objet du Journey Swift de Coddy. Leçon 48 sur 57.
Le builder assemble un objet complexe étape par étape. Chaque étape définit une partie et renvoie le builder, ce qui permet d’enchaîner les étapes, et un dernier build() crée l’objet final :
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")Sortie :
POST /users 1 headerLe builder peut vérifier l’ensemble de la configuration avant de créer l’objet, de sorte qu’un objet invalide n’est jamais construit. Ici, build() lève une erreur lorsqu’un élément requis est manquant :
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)")
}Sortie :
cannot build: missingURLChaque étape copie la structure et modifie la copie, de sorte qu’un constructeur partiellement configuré peut être réutilisé comme modèle pour plusieurs objets :
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())Sortie :
large pizza with cheese, ham
large pizza with cheese, olivesLes valeurs de paramètres par défaut de Swift couvrent déjà les cas simples (Request(url: "/", method: "GET")). Un builder devient utile lorsqu’un objet comporte de nombreuses parties facultatives, des parties qui dépendent les unes des autres ou des règles à vérifier avant qu’il puisse être créé.
// simple : les arguments par défaut suffisent
struct Point { var x = 0, y = 0 }
// complexe : de nombreuses parties optionnelles et validation -> builder
let report = ReportBuilder().title("Q3").add(section: s1).add(section: s2).footer("draft").build()Défi
FacileÉcrivez un constructeur pour les messages électroniques. EmailBuilder comporte les étapes chaînables to(_:) (peut être appelé plusieurs fois pour ajouter des destinataires), subject(_:), body(_:) et urgent(). build() renvoie un Email ou lève EmailError.noRecipients ou EmailError.noSubject. Un e-mail urgent reçoit [URGENT] devant son objet.
Le code fourni lit les commandes pour chaque e-mail, séparées par des lignes --- (to ada@x.io, subject Hello, body text, urgent, puis send) et affiche To: ada@x.io, bo@x.io | [URGENT] Hello | text, error: no recipients ou error: no subject.
Votre code doit être placé dans Email.swift, EmailError.swift et EmailBuilder.swift. main.swift contient le code fourni pour les entrées et sorties et ne peut pas être modifié.
Essayez vous-même
// Code d'entrée/sortie fourni : le garder tel quel
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()
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation orientée objet
1Classes et objets
Travailler avec des fichiersDéfinir des classesMéthodes et selfInitialiseursLes classes sont des référencesRécapitulatif - Carte de bibliothèque4Encapsulation
Contrôle d’accèsLecture seule depuis l’extérieurProtection de l’étatInitialiseurs pouvant échouerRécapitulatif - Compte bancaire7Polymorphisme et génériques
PolymorphismeConversion de typeFonctions génériquesTypes génériquesRécapitulatif - Calculateur de formes2Structures et types valeur
StructuresSémantique de valeurMéthodes mutantesStructure ou classeRécapitulatif - article d’inventaire5Héritage
Sous-classesRedéfinition des méthodesInitialiseurs et superfinal et hiérarchiesRécapitulatif - hiérarchie des employés8Protocoles standard
CustomStringConvertibleEquatableComparableHashableRécapitulatif - Tri personnalisé11Projet : Gestion de bibliothèque
Livres et membresEmprunter des livresEntraînez-vous par vous-même : Playground Swift