Паттерн Адаптер
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 92 из 107.
Паттерн Adapter позволяет несовместимым интерфейсам работать вместе, оборачивая существующий тип новым интерфейсом. В то время как Command инкапсулирует действия, Adapter преобразует один интерфейс в другой, который ожидают клиенты.
Представьте, что у вас есть legacy-принтер, который использует другую сигнатуру метода, чем ожидает ваше application:
// Целевой интерфейс, который использует ваше приложение
type Printer interface {
Print(text string) string
}
// Устаревший принтер с несовместимым интерфейсом
type OldPrinter struct{}
func (o OldPrinter) PrintDocument(doc string, copies int) string {
return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}adapter оборачивает legacy-тип и реализует ожидаемый интерфейс:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}Теперь legacy printer без проблем работает с кодом, который ожидает интерфейс Printer:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"Паттерн Adapter бесценен при интеграции сторонних библиотек, работе с legacy-кодом или когда вам нужно использовать существующие классы, которые не соответствуют требованиям вашего interface. Он служит мостом, не изменяя исходный код.
Задание
ЛегкоДавайте создадим систему медиаплеера с использованием паттерна Adapter! У вас есть современный аудиоплеер, который ожидает унифицированный интерфейс, но вам необходимо интегрировать устаревшее аудиооборудование, использующее совершенно другие сигнатуры методов. Ваш адаптер устранит этот разрыв, позволяя старому оборудованию беспрепятственно работать с новой системой.
Вы организуете код в трёх файлах:
player.go: определите целевой интерфейс и устаревшее аудиоустройство.Создайте интерфейс
AudioPlayer, который ожидает ваша современная система, с методомPlay(track string) string, принимающим название трека и возвращающим сообщение о воспроизведении.Также определите устаревшее устройство, которое не соответствует этому интерфейсу:
- структура
VinylPlayerс полемSpeedRPM(int). У неё есть методDropNeedle(record string, side string) string, который возвращаетPlaying [record] ([side] side) at [speed]rpm
Интерфейс VinylPlayer несовместим с AudioPlayer. Он требует два параметра и использует совершенно другое имя метода.
- структура
adapter.go: создайте адаптер, который сделает устаревшее устройство совместимым.Создайте структуру
VinylAdapter, которая оборачиваетVinylPlayerи реализует интерфейсAudioPlayer. Когда вызываетсяPlay, адаптер должен вызвать у винилового плеера методDropNeedle, подставляяAпо умолчанию в качестве параметра стороны.Также создайте современный плеер для сравнения:
- структура
DigitalPlayer: её методPlayвозвращаетStreaming: [track]
- структура
main.go: продемонстрируйте работу обоих плееров через один и тот же интерфейс.Создайте функцию
PlayMusic(player AudioPlayer, track string) string, которая использует интерфейс для воспроизведения музыки. Эта функция не должна знать или учитывать, используется ли цифровой плеер или адаптированный виниловый плеер.Прочитайте тип плеера (
digitalилиvinyl), а если выбран vinyl, также прочитайте скорость RPM. Затем прочитайте название трека. Создайте соответствующий плеер (используя адаптер для vinyl), воспроизведите трек с помощью функцииPlayMusicи выведите результат.
Будут предоставлены следующие входные данные:
- Строка 1: тип плеера (
digitalилиvinyl) - Строка 2: скорость RPM (только если выбран vinyl)
- Последняя строка: название воспроизводимого трека
Например, если даны:
digital
Bohemian RhapsodyВаш вывод должен быть таким:
Streaming: Bohemian RhapsodyА если даны:
vinyl
33
Abbey RoadВаш вывод должен быть таким:
Playing Abbey Road (A side) at 33rpmА если даны:
vinyl
45
Blue MondayВаш вывод должен быть таким:
Playing Blue Monday (A side) at 45rpmОбратите внимание, как функция PlayMusic работает одинаково с обоими плеерами. Она не знает, что виниловый плеер на самом деле адаптируется из совершенно другого интерфейса. Адаптер преобразует вызовы современного интерфейса в устаревший формат в фоновом режиме!
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// PlayMusic использует интерфейс AudioPlayer для воспроизведения музыки
// Эта функция работает с любой реализацией AudioPlayer
// TODO: Реализуйте эту функцию, чтобы вызвать player.Play(track) и вернуть результат
func PlayMusic(player AudioPlayer, track string) string {
// TODO: Реализуйте эту функцию
return ""
}
func main() {
reader := bufio.NewReader(os.Stdin)
// Читаем тип плеера
playerType, _ := reader.ReadString('\n')
playerType = strings.TrimSpace(playerType)
var player AudioPlayer
var track string
if playerType == "vinyl" {
// Читаем скорость RPM для винила
rpmStr, _ := reader.ReadString('\n')
rpmStr = strings.TrimSpace(rpmStr)
rpm, _ := strconv.Atoi(rpmStr)
// Читаем название трека
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: Создайте VinylPlayer с заданным RPM
// TODO: Оберните его в VinylAdapter
// TODO: Присвойте переменной player
_ = rpm // Удалите эту строку, когда используете rpm
} else {
// Читаем название трека
track, _ = reader.ReadString('\n')
track = strings.TrimSpace(track)
// TODO: Создайте DigitalPlayer
// TODO: Присвойте переменной player
}
// TODO: Вызовите PlayMusic и выведите результат
_ = player // Удалите эту строку, когда будете использовать player
_ = track // Удалите эту строку, когда будете использовать track
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker PoolПотренируйтесь самостоятельно: Онлайн-компилятор Go