Swift Design Patterns: Adapter

Зачем нужен?

Паттерн Adapter применяется, когда у вас имеется готовый компонент, реализующий требуемую функциональность, но его API несовместимо с философией/архитектурой остальной системы.

Определение

  • преобразует интерфейс класса к другому интерфейсу, на который рассчитан клиент. Адаптер обеспечивает совместную работу классов, невозможную в обычных условиях из-за несовместимости интерфейсов.
  • обертывает(упаковывает) существующий класс в новый интерфейс
  • приводит в соответствие старый компонент к новой системе

Реализация

Переиспользование кода очень часто является болезненным и труднодостижимым процессом. Одна из проблем - проектирование чего-то нового, при этом используя что-то старое. Но всегда есть какое-то несоответствие между новым и старым. Тут-то и пригодится связующий компонент, адаптер, который приведет в соответствие старый компонент к новой системе.

Клиенты вызывают методы у объекта Адаптера, который перенаправляет вызовы к унаследованному компоненту. Адаптер может быть реализован с помощью наследования(адаптер классов) или с помощью композиции(агрегации). Паттерн Адаптер и адаптер в реальной мире - по сути одно и тоже.

Реализация похожа на паттерн фасад, но задачи у них разные.

  1. Идентифицируйте клиента(компонент к которому надо приспосабливаться) и адаптируемый объект.
  2. Определите интерфейс, который требует клиент
  3. Спроектируйте класс-адаптер: он должен содержать адаптируемый объект(композиция) и делегировать интерфейс клиента адаптируемому объекту

Пример

Допустим у нас есть утки. Но уток мало и мы хотим в качестве уток использовать еще и индейку. Индейка не может крякать, но может гоготать. Также она не может далеко летать.

Идентифицируем клиента и его протокол(интерфейс):

protocol Duck {
    func quack()
    func fly()
}

// клиент
class MallardDuck: Duck {
    func quack() {
        print("Quack-quack")
    }
    
    func fly() {
        print("I'm flying")
    }
}

Класс, который мы хотим адаптировать:

protocol Turkey {
    func gobble()
    func fly()
}

// адаптируемый интерфейс
class WildTurkey: Turkey {
    func gobble() {
        print("Gobble-gobble")
    }
    
    func fly() {
        print("I'm flying but only on a short distances :(")
    }
}

Спроектируем адаптер

// адаптер
class TurkeyAdapter: Duck {
    private let turkey: Turkey
    
    init(turkey: Turkey) {
        self.turkey = turkey
    }
    
    func quack() {
        turkey.gobble()
    }
    
    func fly() {
        for _ in 0..<5 {
            turkey.fly()
        }
    }
}

Теперь мы можем спокойно использовать индейку и даже не подозревать, что это индейка.

Реализация паттерна Adapter: