Паттерн Адаптер
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 92 из 107.
Паттерн Адаптер позволяет несовместимым интерфейсам работать вместе, оборачивая существующий тип в новый интерфейс. В то время как Команда инкапсулирует действия, Адаптер преобразует один интерфейс в другой, который ожидают клиенты.
Представьте, что у вас есть устаревший принтер, который использует сигнатуру метода, отличную от той, которую ожидает ваше приложение:
// Целевой интерфейс, который использует ваше приложение
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)
}Адаптер оборачивает устаревший тип и реализует ожидаемый интерфейс:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}Теперь устаревший принтер бесперебойно работает с кодом, ожидающим интерфейс Printer:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"Паттерн Адаптер неоценим при интеграции сторонних библиотек, работе с устаревшим кодом или когда вам нужно использовать существующие классы, которые не соответствуют требованиям вашего интерфейса. Он выступает в роли моста, не требуя изменения исходного кода.
Задание
ЛегкоДавайте создадим систему медиаплеера, используя паттерн Адаптер! У вас есть современный аудиоплеер, который ожидает унифицированный интерфейс, но вам нужно интегрировать устаревшее аудиооборудование, использующее совершенно другие сигнатуры методов. Ваш адаптер устранит этот разрыв, позволяя старому оборудованию бесперебойно работать с новой системой.
Вы распределите свой код по трем файлам:
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"в качестве значения по умолчанию для параметра стороны (side).Также создайте современный плеер для сравнения:
- Структура
DigitalPlayer— ее методPlayвозвращаетStreaming: [track]
- Структура
main.go: Продемонстрируйте работу обоих плееров через один и тот же интерфейс.Создайте функцию
PlayMusic(player AudioPlayer, track string) string, которая использует интерфейс для воспроизведения музыки — эта функция не должна знать или заботиться о том, использует ли она цифровой плеер или адаптированный виниловый проигрыватель.Считайте тип плеера (
"digital"или"vinyl"), и если это винил, также считайте скорость RPM. Затем считайте название трека. Создайте соответствующий плеер (используя адаптер для винила), воспроизведите трек через функциюPlayMusicи выведите результат.
Будут предоставлены следующие входные данные:
- Строка 1: Тип плеера (
"digital"или"vinyl") - Строка 2: Скорость RPM (только если винил)
- Последняя строка: Название трека для воспроизведения
Например, для входных данных:
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 работает одинаково с обоими плеерами — она понятия не имеет, что виниловый проигрыватель на самом деле адаптирован из совершенно другого интерфейса. Адаптер переводит вызовы современного интерфейса в устаревший формат за кулисами!
Шпаргалка
Паттерн Адаптер (Adapter) позволяет несовместимым интерфейсам работать вместе, оборачивая существующий тип в новый интерфейс.
Определите целевой интерфейс, который ожидает ваше приложение:
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)
}Создайте адаптер, который оборачивает устаревший тип и реализует целевой интерфейс:
type OldPrinterAdapter struct {
oldPrinter OldPrinter
}
func (a OldPrinterAdapter) Print(text string) string {
return a.oldPrinter.PrintDocument(text, 1)
}Используйте адаптер, чтобы заставить устаревший тип работать с кодом, ожидающим целевой интерфейс:
func PrintMessage(p Printer, msg string) string {
return p.Print(msg)
}
adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello") // "Old printer: Hello (x1)"Паттерн Адаптер полезен для интеграции сторонних библиотек, работы с устаревшим кодом или адаптации существующих классов под требуемые интерфейсы без изменения исходного кода.
Попробуйте сами
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