Padrão Builder
Parte da seção Programação Orientada a Objetos do Journey de Swift da Coddy. Lição 48 de 57.
O padrão builder monta um objeto complexo passo a passo. Cada etapa define uma parte e retorna o builder, permitindo encadear as etapas; então, um build() final cria o objeto concluído:
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")Saída:
POST /users 1 headerO builder pode verificar toda a configuração antes de criar o objeto, portanto um objeto inválido nunca é construído. Aqui, build() lança um erro quando uma parte obrigatória está ausente:
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)")
}Saída:
cannot build: missingURLCada etapa copia a struct e altera a cópia, portanto um builder parcialmente configurado pode ser reutilizado como um modelo para vários 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())Saída:
large pizza with cheese, ham
large pizza with cheese, olivesOs valores padrão dos parâmetros do Swift já abrangem casos simples (Request(url: "/", method: "GET")). Um builder é vantajoso quando um objeto tem muitas partes opcionais, partes que dependem umas das outras ou regras que precisam ser verificadas antes que ele possa existir.
// simples: argumentos padrão são suficientes
struct Point { var x = 0, y = 0 }
// complexo: muitas partes opcionais e validação -> builder
let report = ReportBuilder().title("Q3").add(section: s1).add(section: s2).footer("draft").build()Desafio
FácilEscreva um construtor para mensagens de e-mail. EmailBuilder possui etapas encadeáveis to(_:) (pode ser chamado várias vezes, adicionando destinatários), subject(_:), body(_:) e urgent(). build() retorna um Email ou lança EmailError.noRecipients ou EmailError.noSubject. Um e-mail urgente recebe [URGENT] antes do assunto.
O código fornecido lê comandos para cada e-mail, separados por linhas --- (to ada@x.io, subject Hello, body text, urgent e depois send) e imprime To: ada@x.io, bo@x.io | [URGENT] Hello | text, error: no recipients ou error: no subject.
Seu código deve estar em Email.swift, EmailError.swift e EmailBuilder.swift. main.swift contém o código de entrada/saída fornecido e não pode ser editado.
Experimente você mesmo
// Código de entrada/saída fornecido: mantenha-o 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 lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Classes e Objetos
Trabalhando com ArquivosDefinindo ClassesMétodos e selfInicializadoresClasses são ReferênciasRevisão - Cartão da Biblioteca4Encapsulamento
Controle de AcessoSomente Leitura ExternaProtegendo o EstadoInicializadores que Podem FalharRevisão - Conta Bancária7Polimorfismo e Genéricos
PolimorfismoConversão de TiposFunções GenéricasTipos GenéricosRevisão - Calculadora de Formas2Structs e Tipos de Valor
StructsSemântica de ValorMétodos MutáveisStruct ou ClasseRecapitulação - Item de Inventário5Herança
SubclassesSobrescrita de métodosInicializadores e superfinal e hierarquiasRecapitulação - Hierarquia de funcionários8Protocolos Padrão
CustomStringConvertibleEquatableComparableHashableRecapitulação - Ordenação Personalizada11Projeto: Gerenciamento de Biblioteca
Livros e MembrosEmpréstimo de LivrosPratique por conta própria: Playground de Swift