Golang. Паттерн Adapter

Вернувшись в очередной раз к Golang-программированию в свободное от жизни время, решил потратить его с пользой и написать серию статей по паттернам программирования на примере этого языка. Вдохновила меня на это другая работа — Шпаргалка по шаблонам проектирования. Всем советую ее, пользуюсь много лет — человек реально собрал все в одном месте — для тех кому нужно только вспомнить концепт. Надеюсь автор не обидится за то, что позаимствую картинки для общего блага.

В первой статье цикла рассмотрим один из самых простых паттернов — Adapter. Когда его используем:

  • имеется какой то набор классов, методы которых необходимо использовать в конкретном месте

  • классы имеют разные сигнатуры методов, которые мы хотим позвать

  • имеется общая желаемая сигнатура для вызова каждого метода

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

Диаграмма паттерна Adapter

Диаграмма паттерна Adapter

Пример кода AS IS

Первым делом просто трансформируем диаграмму в код, чтобы осознать функции элементов.

Adaptee — адаптируемый класс, методы которого необходимо вызвать в другом месте — Client.

// адаптируемая класс
type TAdaptee struct {}

// Метод адаптируемого класса, который нужно вызвать где-то
func (adapter TAdaptee) AdaptedOperation() {
    fmt.Println("I am AdaptedOperation()")
}

ConcreteAdapter — является оболочкой для Adaptee (включает его как атрибут) и содержит метод удовлетворяющий сигнатуре, которую хотим использовать в Client для вызова.

// класс конкретного адаптера
type ConcreteAdapter struct{
    adaptee TAdaptee
}

// реализация метода интерфейса, реализующего вызов адаптируемого класса
func (adapter ConcreteAdapter) Operation() {
    adapter.adaptee.AdaptedOperation()
}

Adapter — интерфейс, который описывает желаемые сигнатуры методов для использования в Client. Хочу обратить особое внимание, что во многих статьях в сети этот пункт упускают. Без интерфейса паттерн бесполезен. Это очень важная часть — далее будет пояснение почему.

// интерфейс классов адаптера
type Adapter interface {
    Operation()
}

Так как наш интерфейс имеет метод Operation, то класс ConcreteAdapter автоматом становится его потомком, реализуя данный интерфейс.

Ну и применение:

// основной метод для демонстрации (Client)
func main() {
    fmt.Println("\nAdapter demo:\n")
    // создаем перемнную типа интерфейс
    var adapter Adapter
    // присваиваем переменной конкретный экземпляр адаптера
    adapter = ConcreteAdapter{}
    // вызваем желаемый метод
    adapter.Operation()
}

Собственно и все. Желаемый метод вызван с требуемой сигнатурой:

Результат

Результат

Полный код примера

package main

import (
    "fmt"
)

// адаптируемая класс
type TAdaptee struct {}

// Метод адаптируемого класса, который нужно вызвать где-то
func (adapter TAdaptee) AdaptedOperation() {
    fmt.Println("I am AdaptedOperation()")
}

// интерфейс классов адаптера
type Adapter interface {
    Operation()
}

// класс конкретного адаптера
type ConcreteAdapter struct{
    adaptee TAdaptee
}

// реализация метода интерфейса, реализующего вызов адаптируемого класса
func (adapter ConcreteAdapter) Operation() {
    adapter.adaptee.AdaptedOperation()
}

// основной метод для демонстрации
func main() {
    fmt.Println("\nAdapter demo:\n")
    var adapter Adapter
    adapter = ConcreteAdapter{}
    adapter.Operation()
} 

Все любят котиков

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

Имеется у нас два типа животины:

// класс собака
type TDog struct {}

// реакция собаки
func (dog TDog) WoofWoof() {
    fmt.Println("Гав-Гав. Хозяин, дай пожрать")
}

// класс кошка
type TCat struct {}

// реакция кошки, она немного посложнее и если ее не позвать - она молчит
func (dog TCat) MeowMeow(isCall bool) {
    if isCall {
        fmt.Println("Где моя еда, раб? Ну так уж и быть... Мяу-мяу")
    }
}

Собака — она верная. С ней все просто, чуть что — сразу гав-гав. А усатую бестию еще позвать нужно. Мало того, что бормочут непонятно на каком языке, так еще и сигнатура параметров отличается.

Ну мыжпрограммисты. Пишем адаптеры и обязательно интерфейс:

// интерфейс 
type AnimalAdapter interface {
    Reaction()
}

// адаптер для собаки
type DogAdapter struct{
    dog TDog
}

// реакция собаки
func (adapter DogAdapter) Reaction() {
    adapter.dog.WoofWoof()
}

// адаптер для кошки
type CatAdapter struct{
    cat TCat
}

// реакция кошки
func (adapter CatAdapter) Reaction() {
    // адаптер автоматически зовет кошку isCall = true
    adapter.cat.MeowMeow(true)
}

Для приведения сигнатур к общему виду мы решили встроить в адаптер кошачий автопризыватель. Нужно тебе лохматого найти — адаптер сам его позовет. В качестве общей сигнатуры вызова выбрана — Reaction ().

Ну и собственно каждый раз, когда мы подходим к двери — наша задача вставить в ухо устройство, с двумя чипами-адаптерами и все. Как только мы окажемся дома, сразу станет понятно чего от нас хотят.

/*
* основной метод для демонстрации
*/
func main() {
    fmt.Println("\nВы останавливаетесь перед дверью и вставляете в ухо адаптер с двумя чипами\n")
    myAnimalAdapters := [2]AnimalAdapter{DogAdapter{}, CatAdapter{}}
    //
    fmt.Println("Открываете дверь и заходите домой\n")
    for _, adapter := range myAnimalAdapters {
        adapter.Reaction()
    }
} 

Лучше бы я не делал этого…

Результат

Результат

Собственно из примера понятно, зачем нужен интерфейс — чтобы не создавать переменные разных типов, а работать с одной абстрактной сущностью для вызова нужных методов. Это важный аспект паттерна при его использовании. В данном случае мы хоть и имеем массив myAnimalAdapters, однако у него один тип — AnimalAdapter. А от если бы у нас были разные типы — две переменные типов адаптеров (CatAdapter, DogAdapter) — то это уже китайский адаптер.

Полный код

package main

import (
    "fmt"
)

/*
* группа классов, которые мы адаптируем
*/

// класс собака
type TDog struct {}

// реакция собаки
func (dog TDog) WoofWoof() {
    fmt.Println("Гав-Гав. Хозяин, дай пожрать")
}

// класс кошка
type TCat struct {}

// реакция кошки, она немного посложнее и если ее не позвать - она молчит
func (dog TCat) MeowMeow(isCall bool) {
    if isCall {
        fmt.Println("Где моя еда, раб? Ну так уж и быть... Мяу-мяу")
    }
}

/*
* интерфейс адаптера и адаптеры для животных
*/

// интерфейс 
type AnimalAdapter interface {
    Reaction()
}

// адаптер для собаки
type DogAdapter struct{
    dog TDog
}

// реакция собаки
func (adapter DogAdapter) Reaction() {
    adapter.dog.WoofWoof()
}

// адаптер для кошки
type CatAdapter struct{
    cat TCat
}

// реакция кошки
func (adapter CatAdapter) Reaction() {
    // адаптер автоматически зовет кошку isCall = true
    adapter.cat.MeowMeow(true)
}

/*
* основной метод для демонстрации
*/
func main() {
    fmt.Println("\nВы останавливаетесь перед дверью и вставляете в ухо адаптер с двумя чипами\n")
    myAnimalAdapters := [2]AnimalAdapter{DogAdapter{}, CatAdapter{}}
    //
    fmt.Println("Открываете дверь и заходите домой\n")
    for _, adapter := range myAnimalAdapters {
        adapter.Reaction()
    }
} 

© Habrahabr.ru