Menu
Coddy logo textTech

Patrón decorador

Parte de la sección Programación orientada a objetos del Journey de Swift de Coddy. Lección 47 de 57.

El patrón decorator añade comportamiento a un objeto envolviéndolo en otro objeto con el mismo protocolo. El envoltorio reenvía la llamada al objeto envuelto y añade su propia parte antes o después:

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())

Salida:

espresso + milk
250

Como un decorador es en sí mismo un Coffee, los decoradores pueden envolverse entre sí en cualquier combinación y orden, sin una subclase para cada combinación:

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())

Salida:

espresso + milk + milk + syrup
330

Los decoradores permiten añadir funcionalidades transversales, como registro, almacenamiento en caché o validación, alrededor de un componente existente, que permanece sin cambios:

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))

Salida:

fetching 7
row 7

El código que utiliza el objeto solo ve el protocolo, por lo que no puede distinguir un objeto decorado de uno simple. Eso es lo que hace que el patrón se pueda añadir más adelante de forma segura.

func printReceipt(_ c: any Coffee) {      // funciona para Espresso y para cualquier versión envuelta
    print("\(c.label()): \(c.cost())")
}
challenge icon

Desafío

Fácil

Las pizzas se construyen con decoradores. El protocolo Pizza tiene price() y toppings() (un array de nombres). Base cuesta 800 y no tiene ingredientes. Implementa los decoradores Cheese (+150), Olives (+100) y Double (duplica el precio de todo lo que contiene y repite dos veces sus ingredientes). Completa build(_:), que comienza con Base y la envuelve con un decorador por cada nombre, en orden.

El código proporcionado lee líneas de nombres de ingredientes separados por comas (o una línea vacía), construye cada pizza e imprime 1050: cheese, olives o 800: plain.

Tu código va en Pizza.swift, Base.swift, Cheese.swift, Olives.swift, Double.swift y Functions.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: mantenlo 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 iconPonte a prueba

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

Practica por tu cuenta: Playground de Swift