Menu
Coddy logo textTech

Паттерн Адаптер

Часть раздела Объектно-ориентированное программирование путешествия по 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)"

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

challenge icon

Задание

Легко

Давайте создадим систему медиаплеера, используя паттерн Адаптер! У вас есть современный аудиоплеер, который ожидает унифицированный интерфейс, но вам нужно интегрировать устаревшее аудиооборудование, использующее совершенно другие сигнатуры методов. Ваш адаптер устранит этот разрыв, позволяя старому оборудованию бесперебойно работать с новой системой.

Вы распределите свой код по трем файлам:

  • 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
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование