Шаблоны программирования на Swift: Adapter

adapter_proswift_ru

Тема сегодняшней статьи из цикла шаблоны программирования на Swift — шаблон Adapter.

Adapter

Как и раньше, сначала смотрим что нам говорит по этому поводу книга:

Представьте, что Вы едете в коммандировку в США. У Вас есть, допустим, ноутбук купленный в Европе – следовательно вилка на проводе от блока питания имеет круглые окончания. Что делать? Покупать зарядку для американского типа розетки? А когда вы вернетесь домой – она будет лежать у Вас мертвым грузом?

Потому, вероятнее всего, Вы приобретете один из адаптеров, которые надеваются на вилку, и которая позволяет Вам использовать старую зарядку и заряжаться от совершенно другой розетки.

Примечание автора: суть вышеизложенной проблемы отображает заглавная картинка к данной статье, однако стоит иметь ввиду что картинка рисовалась явно не российским художником. Т.к. то что художник назвал стандартной вилкой — явно введет в ступор российского пользователя. Но это все лишь формальности…

Так и с Адаптером – он конвертит интерфейс класса – на такой, который ожидается.

Сам паттерн состоит из трех частей: Цели (target), Адаптера (adapter), и адаптируемого (adaptee).

В нашей с вами проблеме:

  1. Target – ноутбук со старой зарядкой
  2. Adapter – переходник.
  3. 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. Передаем координаты в новом формате и через адаптер получаем и в старом.

Метки:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.