Menu
Coddy logo textTech

Padrão de Delegação

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

Delegação permite que um objeto transfira algumas decisões para outro objeto, seu delegado, por meio de um protocolo. O objeto realiza seu trabalho e consulta o delegado nos pontos que variam. Os frameworks da Apple usam esse padrão em todos os lugares:

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

Saída:

50%
100%
done: photo.png

A propriedade delegate é opcional e as chamadas usam delegate?., portanto o objeto funciona mesmo quando ninguém está escutando. Ela é declarada como weak para que o objeto não mantenha seu delegate ativo por conta própria, o que evita ciclos de retenção entre objetos que apontam uns para os outros:

weak var delegate: DownloadDelegate?   // requer que o protocolo seja apenas de classe: AnyObject
delegate?.finished(file)               // não faz nada quando não há delegate

Um delegado também pode responder a perguntas, o que permite personalizar o comportamento do objeto sem criar uma subclasse:

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

Saída:

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

A delegação difere do padrão observer: um objeto tem um único delegado a quem faz perguntas e informa, enquanto um sujeito pode ter muitos observadores aos quais apenas informa.

// delegação: um parceiro, pode responder perguntas
weak var delegate: TableDelegate?
// observador: qualquer número de ouvintes, apenas notificados
private var observers: [(Event) -> Void] = []
challenge icon

Desafio

Fácil

Um Checkout pede decisões ao seu delegado. O protocolo CheckoutDelegate (somente para classes) possui discount(for total: Int) -> Int e didComplete(total: Int). Checkout tem uma weak var delegate, e pay(_ prices: [Int]) -> Int soma os preços, subtrai o desconto do delegado (0 sem um delegado), informa ao delegado o total final e o retorna. Implemente LoyaltyShop, um delegado que oferece 10% de desconto para totais de 100 ou mais e registra cada total concluído em receipts.

O código fornecido lê linhas de preços separados por vírgulas. Ele paga a primeira linha sem um delegado e as demais com um LoyaltyShop, imprime cada total e, por fim, receipts: 120 90.

Seu código deve estar em CheckoutDelegate.swift, Checkout.swift e LoyaltyShop.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) }
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 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