Menu
Coddy logo textTech

Patrón de delegación

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

La delegación permite que un objeto deje algunas decisiones en manos de otro objeto, su delegado, mediante un protocolo. El objeto realiza su tarea y consulta al delegado en los puntos que varían. Los frameworks de Apple utilizan este patrón en todas partes:

protocol DownloadDelegate: AnyObject {
    func progress(_ percent: Int)
    func finished(_ file: String)
}

final class Downloader {
    weak var delegate: DownloadDelegate?

    func download(_ file: String) {
        for p in [50, 100] { delegate?.progress(p) }
        delegate?.finished(file)
    }
}

final class Screen: DownloadDelegate {
    func progress(_ percent: Int) { print("\(percent)%") }
    func finished(_ file: String) { print("done: \(file)") }
}

let screen = Screen()
let d = Downloader()
d.delegate = screen
d.download("photo.png")

Salida:

50%
100%
done: photo.png

La propiedad delegate es opcional y las llamadas usan delegate?., por lo que el objeto funciona incluso cuando nadie está escuchando. Se declara weak para que el objeto no mantenga vivo a su delegate por sí mismo, lo que evita ciclos de retención entre objetos que apuntan el uno al otro:

weak var delegate: DownloadDelegate?   // requiere que el protocolo sea solo de clase: AnyObject
delegate?.finished(file)               // no hace nada cuando no hay delegate

Un delegado también puede responder preguntas, lo que le permite personalizar el comportamiento del objeto sin crear una subclase:

protocol ListDelegate: AnyObject {
    func shouldShow(_ item: String) -> Bool
}

final class ListView {
    weak var delegate: ListDelegate?
    func render(_ items: [String]) -> [String] {
        items.filter { delegate?.shouldShow($0) ?? true }
    }
}

final class HideDrafts: ListDelegate {
    func shouldShow(_ item: String) -> Bool { !item.hasPrefix("draft") }
}

let view = ListView()
let rules = HideDrafts()
print(view.render(["a", "draft b"]))
view.delegate = rules
print(view.render(["a", "draft b"]))

Salida:

["a", "draft b"]
["a"]

La delegación se diferencia del patrón de observador: un objeto tiene un delegado al que pregunta y mantiene informado, mientras que un sujeto puede tener muchos observadores a los que solo informa.

// delegación: un socio, puede responder preguntas
weak var delegate: TableDelegate?
// observador: cualquier número de oyentes, solo notificados
private var observers: [(Event) -> Void] = []
challenge icon

Desafío

Fácil

Un Checkout pide decisiones a su delegado. El protocolo CheckoutDelegate (solo para clases) tiene discount(for total: Int) -> Int y didComplete(total: Int). Checkout tiene un weak var delegate, y pay(_ prices: [Int]) -> Int suma los precios, resta el descuento del delegado (0 sin delegado), informa al delegado del total final y lo devuelve. Implementa LoyaltyShop, un delegado que ofrece un 10 % de descuento en totales de 100 o más y registra cada total completado en receipts.

El código proporcionado lee líneas de precios separados por comas. Paga la primera línea sin delegado y las demás con un LoyaltyShop, imprime cada total y finalmente receipts: 120 90.

Tu código debe estar en CheckoutDelegate.swift, Checkout.swift y LoyaltyShop.swift. main.swift contiene el código de entrada/salida proporcionado 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) }
let checkout = Checkout()
let shop = LoyaltyShop()
for (i, line) in input.enumerated() {
    if i == 1 { checkout.delegate = shop }
    print(checkout.pay(line.split(separator: ",").map { Int($0)! }))
}
print("receipts: " + shop.receipts.map { String($0) }.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