Patrón constructor
Parte de la sección Programación orientada a objetos del Journey de Swift de Coddy. Lección 48 de 57.
El patrón builder ensambla un objeto complejo paso a paso. Cada paso establece una parte y devuelve el builder, de modo que los pasos se pueden encadenar, y un build() final crea el objeto terminado:
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")Salida:
POST /users 1 headerEl builder puede comprobar toda la configuración antes de crear el objeto, por lo que nunca se construye un objeto no válido. Aquí build() lanza un error cuando falta una parte obligatoria:
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)")
}Salida:
cannot build: missingURLCada paso copia la estructura y cambia la copia, por lo que un constructor configurado parcialmente puede reutilizarse como plantilla para varios objetos:
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())Salida:
large pizza with cheese, ham
large pizza with cheese, olivesLos valores de parámetros predeterminados de Swift ya cubren los casos simples (Request(url: "/", method: "GET")). Un builder resulta útil cuando un objeto tiene muchas partes opcionales, partes que dependen unas de otras o reglas que deben comprobarse antes de que pueda existir.
// simple: los argumentos por defecto son suficientes
struct Point { var x = 0, y = 0 }
// complex: muchas partes opcionales y validación -> builder
let report = ReportBuilder().title("Q3").add(section: s1).add(section: s2).footer("draft").build()Desafío
FácilEscribe un builder para mensajes de correo electrónico. EmailBuilder tiene pasos encadenables to(_:) (se puede llamar varias veces para añadir destinatarios), subject(_:), body(_:) y urgent(). build() devuelve un Email o lanza EmailError.noRecipients o EmailError.noSubject. Un correo urgente recibe [URGENT] delante de su asunto.
El código proporcionado lee comandos para cada correo, separados por líneas --- (to ada@x.io, subject Hello, body text, urgent, después send) e imprime To: ada@x.io, bo@x.io | [URGENT] Hello | text, error: no recipients o error: no subject.
Tu código debe estar en Email.swift, EmailError.swift y EmailBuilder.swift. main.swift contiene el código proporcionado de entrada/salida y no se puede editar.
Pruébalo tú mismo
// Código de entrada/salida suministrado: déjalo como está
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()
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación orientada a objetos
1Clases y objetos
Trabajar con archivosDefinir clasesMétodos y selfInicializadoresLas clases son referenciasRepaso: tarjeta de biblioteca4Encapsulación
Control de accesoSolo lectura desde fueraProtección del estadoInicializadores que pueden fallarRepaso - Cuenta bancaria7Polimorfismo y genéricos
PolimorfismoConversión de tiposFunciones genéricasTipos genéricosRepaso - Calculadora de formas2Structs y tipos por valor
StructsSemántica de valoresMétodos mutablesStruct o claseRepaso - elemento del inventario5Herencia
SubclasesSobrescritura de métodosInicializadores y superfinal y jerarquíasRepaso: jerarquía de empleados8Protocolos estándar
CustomStringConvertibleEquatableComparableHashableRepaso - Ordenamiento personalizado11Proyecto: Gestión de bibliotecas
Libros y miembrosPréstamo de librosPractica por tu cuenta: Playground de Swift