Menu
Coddy logo textTech

Padrão Decorator

Parte da seção Programação Orientada a Objetos do Journey de Swift da Coddy. Lição 47 de 57.

O padrão decorator adiciona comportamento a um objeto encapsulando-o em outro objeto com o mesmo protocolo. O encapsulador encaminha a chamada para o objeto encapsulado e adiciona sua própria parte antes ou depois:

protocol Coffee {
    func cost() -> Int
    func label() -> String
}

struct Espresso: Coffee {
    func cost() -> Int { 200 }
    func label() -> String { "espresso" }
}

struct WithMilk: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 50 }
    func label() -> String { base.label() + " + milk" }
}

let drink = WithMilk(base: Espresso())
print(drink.label())
print(drink.cost())

Saída:

espresso + milk
250

Como um decorador é, por si só, um Coffee, os decoradores podem envolver uns aos outros em qualquer combinação e ordem, sem uma subclasse para cada combinação:

protocol Coffee { func cost() -> Int; func label() -> String }
struct Espresso: Coffee { func cost() -> Int { 200 }; func label() -> String { "espresso" } }
struct WithMilk: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 50 }
    func label() -> String { base.label() + " + milk" }
}
struct WithSyrup: Coffee {
    let base: any Coffee
    func cost() -> Int { base.cost() + 30 }
    func label() -> String { base.label() + " + syrup" }
}

let fancy = WithSyrup(base: WithMilk(base: WithMilk(base: Espresso())))
print(fancy.label())
print(fancy.cost())

Saída:

espresso + milk + milk + syrup
330

Decoradores são adequados para adicionar funcionalidades transversais, como registro, armazenamento em cache ou validação, em torno de um componente existente, que permanece inalterado:

protocol DataSource { func fetch(_ id: Int) -> String }
struct Database: DataSource { func fetch(_ id: Int) -> String { "row \(id)" } }

struct Logged: DataSource {
    let inner: any DataSource
    func fetch(_ id: Int) -> String {
        print("fetching \(id)")
        return inner.fetch(id)
    }
}

let source: any DataSource = Logged(inner: Database())
print(source.fetch(7))

Saída:

fetching 7
row 7

O código que usa o objeto vê apenas o protocolo, portanto não consegue distinguir um objeto decorado de um objeto simples. É isso que torna seguro adicionar o padrão posteriormente.

func printReceipt(_ c: any Coffee) {      // funciona para Espresso e para qualquer versão encapsulada
    print("\(c.label()): \(c.cost())")
}
challenge icon

Desafio

Fácil

As pizzas são construídas com decoradores. O protocolo Pizza tem price() e toppings() (um array de nomes). Base custa 800 e não tem coberturas. Implemente os decoradores Cheese (+150), Olives (+100) e Double (dobra o preço de tudo que está dentro dele e repete suas coberturas duas vezes). Complete build(_:), que começa com Base e o envolve com um decorador por nome, na ordem.

O código fornecido lê linhas com nomes de coberturas separados por vírgulas (ou uma linha vazia), constrói cada pizza e imprime 1050: cheese, olives ou 800: plain.

Seu código deve ficar em Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift e Functions.swift. main.swift contém o código fornecido de entrada/saída 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) }
for line in input {
    let names = line.isEmpty ? [] : line.split(separator: ",").map(String.init)
    let p = build(names)
    let tops = p.toppings()
    print("\(p.price()): " + (tops.isEmpty ? "plain" : tops.joined(separator: ", ")))
}
quiz iconTeste seus conhecimentos

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

Pratique por conta própria: Playground de Swift