Шаблоны программирования на Swift: Chain of responsibility
Давайте разберем шаблон программирования Цепочка ответственности (Chain of responsibility) на Swift в среде разработки XCode
Chain of responsibility
Представьте себе очередь людей которые пришли за посылками. Выдающий посылки человек, дает первую посылку первому в очереди человеку, он смотрит на имя-фамилию на корбке, видит что посылка не для него, и передает посылку дальше. Второй человек делает собственно тоже самое, и так пока не найдется получатель.
Цепочка ответственности (chain of responsibility) – позволяет вам передавать объект по цепочке объектов-обработчиков, пока не будет найден необходимый объект обработчик.
Когда использовать этот паттерн:
- У вас более чем один объект — обработчик.
- У вас есть несколько объектов обработчика, при этом вы не хотите специфицировать который объект должен обрабатывать в даный момент времени.
Как всегда – пример:
Представим что у нас есть конвеер, который обрабатывает различные предметы которые на нем: игрушки, электронику и другие.
Для начала создадим классы объектов которые могут быть обработаны нашими обработчиками:
// // proSwift.ru // class BasicItem { } class Toy: BasicItem { } class Electronics :BasicItem { } class Trash: BasicItem { }
Теперь создадим обработчики:
// // proSwift.ru // protocol BasicHandler { var nextHandler: BasicHandler? {get set} func handleItem(item: BasicItem) }
Наш базовый обработчик умеет обрабатывать объекты типа BasicItem. И самое важное – он может иметь ссылку на следующий обработчик (как люди в нашей очереди, которые передают посылку).
Давайте создадим код обработчика игрушки:
// // proSwift.ru // class ToysHandler: BasicHandler { var nextHandler: BasicHandler? required init(aHandler: BasicHandler) { self.nextHandler = aHandler } func handleItem(item: BasicItem) { if item is Toy { print("Нашли игрушку. Обрабатываем") } else { print("Это не игрушка, передаем на обработку следующему") self.nextHandler!.handleItem(item) } } }
Если обработчик получает объект класса Toy – то он его обрабатывает, если нет – то обработчик передает объект следующему обработчику. По аналогии создадим два следующих обработчика: для электроники, и мусора:
// // proSwift.ru // class ElectronicHandler: BasicHandler { var nextHandler: BasicHandler? required init(aHandler: BasicHandler) { self.nextHandler = aHandler } func handleItem(item: BasicItem) { if item is Electronics { print("Нашли электонику. Обрабатываем") } else { print("Это не электроника, передаем на обработку следующему") self.nextHandler!.handleItem(item) } } } class OtherItemsHandler: BasicHandler { var nextHandler: BasicHandler? func handleItem(item: BasicItem) { print("Нашли неопознанный объект. Уничтожаем его") } }
Как видим OtherItemsHandler в случае, когда до него дошло дело – объект уничтожает, и не пробует дергать следующий обработчик (последний человек в очереди не проверяет получателя — его функция просто выбросить посылку, если она попала ему в руки).
Давайте тестировать:
// // proSwift.ru // let otherItemsHandler = OtherItemsHandler() let electronicItemsHandler = ElectronicHandler(aHandler: otherItemsHandler) let toysItemsHandler = ToysHandler(aHandler: electronicItemsHandler) let toy = Toy() let el = Electronics() let trash = Trash() toysItemsHandler.handleItem(toy) print("- - - - -") toysItemsHandler.handleItem(el) print("- - - - -") toysItemsHandler.handleItem(trash)
Мы в начале создаем обработчик мусора, т.к. все предыдущие обработчики инициализируются с ссылкой на предыдущий обработчик. Таким образом все три обработчика скреплены в цепь и ссылаются один на другой, кроме последнего. Его свойство nexhtHandler = nil. Далее создаем объекты и пытаемся обработать эти различные созданные элементы. Традиционно лог:
Нашли игрушку. Обрабатываем - - - - - Это не игрушка, передаем на обработку следующему Нашли электонику. Обрабатываем - - - - - Это не игрушка, передаем на обработку следующему Это не электроника, передаем на обработку следующему Нашли неопознанный объект. Уничтожаем его
Пример с GitHub
Цепочка обязанностей используется для обработки разнообразных запросов, каждый из которых может рассматриваться другим обработчиком.
В примере ниже решается задача для банкомата. В нем есть пачки купюр, определенного достоинства, и нужно решить сможет ли банкомат выдать запрошенную сумму, имея в наличии пачки купюр с определенным количеством и достоинством в этой пачке.
// // proSwift.ru // // https://github.com/ochococo/Design-Patterns-In-Swift/blob/master/source/behavioral/chain_of_responsibility.swift class MoneyPile { let value: Int // достоинтсво купюр в пачке var quantity: Int // количество купюр в пачке var nextPile: MoneyPile? // ссылка на следующую пачку init(value: Int, quantity: Int, nextPile: MoneyPile?){ self.value = value self.quantity = quantity self.nextPile = nextPile } // метод проверки - сможем ли мы выдать нужную сумму используя текущщую пачку func canWithdraw(v: Int) -> Bool { var v = v func canTakeSomeBill(want: Int) -> Bool { return (want / self.value) > 0 } var q = self.quantity while canTakeSomeBill(v) { if q == 0 { break } v = v - self.value q = q - 1 } if v == 0 { return true // справились с выдачей текущей пачкой } else if let next = self.nextPile { //а вот если нет, то пробуем выдать купюрами следющей пачки return next.canWithdraw(v) } return false // никак недьзя выдать нужную сумму } } class ATM { private var hundred: MoneyPile private var fifty: MoneyPile private var twenty: MoneyPile private var ten: MoneyPile private var startPile: MoneyPile { return self.hundred } init(hundred: MoneyPile, fifty: MoneyPile, twenty: MoneyPile, ten: MoneyPile) { self.hundred = hundred self.fifty = fifty self.twenty = twenty self.ten = ten } func canWithdraw(value: Int) -> String { return "Can withdraw: \(self.startPile.canWithdraw(value))" } } // Создаем пачки денег от меньшей купюры к большей 10 < 20 < 50 < 100.** let ten = MoneyPile(value: 10, quantity: 6, nextPile: nil) let twenty = MoneyPile(value: 20, quantity: 2, nextPile: ten) let fifty = MoneyPile(value: 50, quantity: 2, nextPile: twenty) let hundred = MoneyPile(value: 100, quantity: 1, nextPile: fifty) // Создаем банкомат с пачками денег, которые создали ранее var atm = ATM(hundred: hundred, fifty: fifty, twenty: twenty, ten: ten) atm.canWithdraw(310) // Не сможет выдать сумму 310, т.к. банкомат имеет только 300 atm.canWithdraw(100) // Может выдать - 1x100 atm.canWithdraw(165) // Не сможет выдать сумму 165, т.к. банкомат не имеет купюр по 5 atm.canWithdraw(30) // Может выдать - 1x20, 2x10
—
>> atm.canWithdraw(30) // Может выдать — 1×20, 2×10
30: (1×20 & 1×10) || (3×10)