Паттерн делегирования
Часть раздела Объектно-ориентированное программирование путешествия по Swift на Coddy. Урок 46 из 57.
Делегирование позволяет объекту передавать некоторые решения другому объекту — своему делегату — через протокол. Объект выполняет свою работу и обращается к делегату в тех точках, которые могут различаться. Фреймворки Apple используют этот шаблон повсюду:
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")Вывод:
50%
100%
done: photo.pngСвойство делегата является необязательным, а вызовы используют delegate?., поэтому объект работает даже тогда, когда никто не слушает. Оно объявлено как weak, поэтому объект не удерживает своего делегата самостоятельно, что предотвращает циклы удержания между объектами, которые ссылаются друг на друга:
weak var delegate: DownloadDelegate? // требует, чтобы протокол был только для классов: AnyObject
delegate?.finished(file) // ничего не делает, когда нет делегатаДелегат также может отвечать на вопросы, что позволяет настраивать поведение объекта без создания подкласса:
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"]))Результат:
["a", "draft b"]
["a"]Делегирование отличается от шаблона наблюдателя: у объекта есть один делегат, которого он спрашивает и информирует, тогда как у субъекта может быть много наблюдателей, которых он только информирует.
// делегирование: один партнёр, может отвечать на вопросы
weak var delegate: TableDelegate?
// наблюдатель: любое количество слушателей, только уведомляются
private var observers: [(Event) -> Void] = []Задание
ЛегкоCheckout запрашивает решения у своего делегата. Протокол CheckoutDelegate (только для классов) содержит discount(for total: Int) -> Int и didComplete(total: Int). В Checkout есть weak var delegate, а pay(_ prices: [Int]) -> Int суммирует цены, вычитает скидку делегата (0 при отсутствии делегата), сообщает делегату итоговую сумму и возвращает её. Реализуйте LoyaltyShop — делегат, который предоставляет скидку 10% при сумме 100 или больше и записывает каждую завершённую сумму в receipts.
Предоставленный код считывает строки с разделёнными запятыми ценами. Для первой строки выполняется оплата без делегата, а для остальных — с LoyaltyShop; выводится каждая итоговая сумма и в конце receipts: 120 90.
Ваш код должен находиться в CheckoutDelegate.swift, Checkout.swift и LoyaltyShop.swift. В main.swift содержится предоставленный код ввода/вывода, и его нельзя редактировать.
Попробуйте сами
// Предоставленный код ввода/вывода: оставьте его как есть
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: " "))
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовМетоды и selfИнициализаторыКлассы — это ссылкиПовторение — читательский билет4Инкапсуляция
Контроль доступаТолько чтение извнеЗащита состоянияИнициализаторы с возможностью сбояПовторение — банковский счёт7Полиморфизм и обобщения
ПолиморфизмПриведение типовОбобщённые функцииОбобщённые типыПовторение — калькулятор фигур10Паттерны проектирования. Часть 2
Паттерн делегированияПаттерн декораторПаттерн строительПаттерн состояния2Структуры и типы-значения
СтруктурыСемантика значенийИзменяющие методыСтруктура или классПовторение — элемент инвентаря5Наследование
ПодклассыПереопределение методовИнициализаторы и superfinal и иерархииПовторение — иерархия сотрудников8Стандартные протоколы
CustomStringConvertibleEquatableComparableHashableПовторение — пользовательская сортировка11Проект: Управление библиотекой
Книги и читателиВыдача книгПотренируйтесь самостоятельно: Песочница Swift