Шаблоны программирования на Swift: Adapter
Тема сегодняшней статьи из цикла шаблоны программирования на Swift — шаблон Adapter.
Adapter
Как и раньше, сначала смотрим что нам говорит по этому поводу книга:
Представьте, что Вы едете в коммандировку в США. У Вас есть, допустим, ноутбук купленный в Европе – следовательно вилка на проводе от блока питания имеет круглые окончания. Что делать? Покупать зарядку для американского типа розетки? А когда вы вернетесь домой – она будет лежать у Вас мертвым грузом?
Потому, вероятнее всего, Вы приобретете один из адаптеров, которые надеваются на вилку, и которая позволяет Вам использовать старую зарядку и заряжаться от совершенно другой розетки.
Примечание автора: суть вышеизложенной проблемы отображает заглавная картинка к данной статье, однако стоит иметь ввиду что картинка рисовалась явно не российским художником. Т.к. то что художник назвал стандартной вилкой — явно введет в ступор российского пользователя. Но это все лишь формальности…
Так и с Адаптером – он конвертит интерфейс класса – на такой, который ожидается.
Сам паттерн состоит из трех частей: Цели (target), Адаптера (adapter), и адаптируемого (adaptee).
В нашей с вами проблеме:
- Target – ноутбук со старой зарядкой
- Adapter – переходник.
- Adaptee – розетка с квадртаными дырками.
Имплементация паттерна Адаптер на Swift может быть 2.
Простая имплементация Adapter
Итак, первая – это простенькая имплементация. Пускай у нас есть объект Bird, который реализует протокол BirdProtocol:
// // proSwift.ru // protocol BirdProtocol { func sing() func fly() } class Bird: BirdProtocol { func sing() { print("Ку-ка-ре-ку") } func fly() { print("Я лечу-у-у-у!") } }
И пускай у нас есть объект Raven, у которого есть свой интерфейс:
// // proSwift.ru // class Raven { func flySearchAndDestroy() { print("Цель обнаружена! Захожу справа от люсты!") } func vioce() { print("Кар-кар!") } }
Чтобы использовать ворону в методах которые ждут птицу стоит создать адаптер:
// // proSwift.ru // class RavenAdapter: BirdProtocol { private let raven: Raven init(adaptee: Raven) { self.raven = adaptee } func sing() { raven.vioce() } func fly() { raven.flySearchAndDestroy() } }
А тестом работоспособности у нас будет функция, которая в качестве параметра принимает объекты, удовлетворяющие протоколу BirdProtocol
// // proSwift.ru // func makeTheBerdTest(aBird: BirdProtocol) { aBird.fly() aBird.sing() }
Теперь можно запускать тест:
// // proSwift.ru // let simpleBird = Bird() let simpleRaven = Raven() let ravenAdapter = RavenAdapter(adaptee: simpleRaven) makeTheBerdTest(simpleBird) print(" ---- ") makeTheBerdTest(ravenAdapter)
Вполне ожидаемый результат в консоле:
Я лечу-у-у-у! Ку-ка-ре-ку ---- Цель обнаружена! Захожу справа от люсты! Кар-кар!
Adapter через делегирование
Теперь более сложная реализация, которая все еще зависит от протоколов, но уже использует делегацию. Вернемся к нашему несчастному ноутбуку и зарядке: Допустим у нас есть базовый класс Charger:
// // proSwift.ru // class Charger { func charge() { print("Я заряжаю!") } }
И есть протокол для европейской зарядки:
// // proSwift.ru // protocol EuropeanNotebookChargerDelegate: class { func cargeNotebookRoundHoles(charger: Charger) }
Если сделать просто реализацию – то получится тоже самое, что и в прошлом примере. Потому мы добавим делегат:
// // proSwift.ru // class EuropeanNotebookCharger: Charger, EuropeanNotebookChargerDelegate { weak var delegate: EuropeanNotebookChargerDelegate? override init() { super.init() delegate = self } override func charge() { delegate?.cargeNotebookRoundHoles(self) super.charge() } func cargeNotebookRoundHoles(charger: Charger) { print("Заряжаю 220 вольт и розеткой с круглыми дырочками") } }
Как видим, у нашего класса есть свойство которое реализует тип EuropeanNotebookChargerDelegate. Так как, наш класс этот протокол реализует, он может свойству присвоить себя.
Теперь, давайте глянем что ж за зверь такой – американская зарядка:
// // proSwift.ru // class USANotebookCharger { func chargeNotebookRectHoles(charger: Charger) { print("Заряжаю розеткой с ПРЯМОУГОЛЬНЫМИ дырочками ") } }
Как видим, в американской зарядке совсем другой метод и мировозрение.
Давайте, создадим адаптер для зарядки:
// // proSwift.ru // class USANottebookEuropeanAdapter: Charger, EuropeanNotebookChargerDelegate { let usaCharger: USANotebookCharger init(withUSANotebookCharger charger: USANotebookCharger) { self.usaCharger = charger super.init() } override func charge() { let euroCharge = EuropeanNotebookCharger() euroCharge.delegate = self euroCharge.charge() } func cargeNotebookRoundHoles(charger: Charger) { usaCharger.chargeNotebookRectHoles(charger) } }
Адптер реализует интерфейс EuropeanNotebookChargerDelegate и его метод chargetNotebookRoundHoles. Потому, когда вызывается метод charge – на самом деле создается тип европейской зарядки, ей присвается наш адаптер как делегат, и вызывается ее метод charge. Так как делегатом присвоен наш адаптер, при вызове метода chargetNotebookRoundHoles, будет вызыван этот метод нашего адаптера, который в свою очередь вызывает метод зарядки США.
Давайте посмотрим тест код и вывод лога:
// // proSwift.ru // func makeTheNotebookCharge(aCharger: Charger) { aCharger.charge() } let euroCharger = EuropeanNotebookCharger() makeTheNotebookCharge(euroCharger) let usaChager = USANotebookCharger() let adapter = USANottebookEuropeanAdapter(withUSANotebookCharger: usaChager) makeTheNotebookCharge(adapter)
Консоль выглядит вот так:
Заряжаю 220 вольт и розеткой с круглыми дырочками Я заряжаю! Заряжаю розеткой с ПРЯМОУГОЛЬНЫМИ дырочками Я заряжаю!
Пример с GitHub практически полностью повторяет первый вариант адаптера
Шаблон адаптер используется для обеспечения связи между двумя несовместимыми типами, обернув «адаптируемого» с классом, который поддерживает интерфейс, необходимый клиенту.
// // proSwift.ru // // https://github.com/ochococo/Design-Patterns-In-Swift/blob/master/source/structural/adapter.swift protocol OlderDeathStarSuperLaserAiming { var angleV: NSNumber {get} var angleH: NSNumber {get} } struct DeathStarSuperlaserTarget { let angleHorizontal: Double let angleVertical: Double init(angleHorizontal:Double, angleVertical:Double) { self.angleHorizontal = angleHorizontal self.angleVertical = angleVertical } } struct OldDeathStarSuperlaserTarget : OlderDeathStarSuperLaserAiming { private let target : DeathStarSuperlaserTarget var angleV:NSNumber { return NSNumber(double: target.angleVertical) } var angleH:NSNumber { return NSNumber(double: target.angleHorizontal) } init(_ target:DeathStarSuperlaserTarget) { self.target = target } } let target = DeathStarSuperlaserTarget(angleHorizontal: 14.0, angleVertical: 12.0) let oldFormat = OldDeathStarSuperlaserTarget(target) oldFormat.angleH // 14 oldFormat.angleV // 12
Думаю, тут все понятно. Координаты в старом формате это NSNumber, а в новом — это Double. Передаем координаты в новом формате и через адаптер получаем и в старом.
Добавить комментарий