Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 95 из 110.

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

Представьте, что у вас есть система, которая работает с данными JSON, но вам нужно интегрировать компонент, который выдает только XML. Вместо того чтобы изменять какой-либо из классов, вы создаете адаптер, который осуществляет преобразование между ними:

// Существующий интерфейс, который ожидает ваша система
abstract class JsonData {
  String getJson();
}

// Устаревший класс с несовместимым интерфейсом
class XmlService {
  String getXml() {
    return '<data><name>John</name></data>';
  }
}

// Адаптер делает XmlService совместимым с JsonData
class XmlToJsonAdapter implements JsonData {
  final XmlService _xmlService;

  XmlToJsonAdapter(this._xmlService);

  @override
  String getJson() {
    String xml = _xmlService.getXml();
    // Преобразование XML в JSON (упрощенно)
    return '{"name": "John"}';
  }
}

void main() {
  var xmlService = XmlService();
  var adapter = XmlToJsonAdapter(xmlService);

  print(adapter.getJson());  // {"name": "John"}
}

XmlToJsonAdapter оборачивает XmlService и реализует интерфейс JsonData. Клиентский код работает с привычным интерфейсом JsonData, не зная о том, что данные изначально поступили из XML-источника. Адаптер выполняет все преобразования внутри себя.

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

challenge icon

Задание

Легко

Давайте создадим систему адаптера для медиаплеера! Представьте, что у вас есть современная аудиосистема, которая ожидает, что все источники медиа будут предоставлять аудио через стандартный интерфейс, но вам нужно интегрировать старый виниловый проигрыватель, у которого совершенно другой способ воспроизведения звука.

Вы создадите два файла для организации реализации паттерна «Адаптер»:

  • media_adapter.dart: Этот файл содержит ваш интерфейс и классы адаптера. Определите абстрактный класс AudioSource, который ожидает ваша современная система — у него должен быть метод String playAudio(), возвращающий аудиовыход в виде строки. Затем создайте класс VinylPlayer, представляющий устаревшее оборудование — у него есть поле trackName и метод String spinRecord(), который возвращает *crackle* Playing vinyl: [trackName] *crackle*. Наконец, создайте VinylAdapter, который реализует AudioSource и оборачивает VinylPlayer. Метод адаптера playAudio() должен вызывать метод проигрывателя винила spinRecord() и возвращать результат, связывая несовместимые интерфейсы.
  • main.dart: Импортируйте файл медиаадаптера и продемонстрируйте, как адаптер заставляет старый виниловый проигрыватель работать с кодом, который ожидает современный интерфейс AudioSource. Создайте VinylPlayer с названием трека Bohemian Rhapsody. Затем создайте VinylAdapter, который оборачивает этот виниловый проигрыватель. Сохраните адаптер в переменной типа AudioSource, чтобы доказать, что он соответствует ожидаемому интерфейсу. Вызовите playAudio() у вашего источника звука и выведите результат.

Прелесть этого паттерна в том, что ваш основной код работает со стандартным интерфейсом AudioSource — ему не нужно знать, что за кулисами всю реальную работу выполняет старый виниловый проигрыватель!

Ожидаемый вывод:

*crackle* Playing vinyl: Bohemian Rhapsody *crackle*

Шпаргалка

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

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

Базовая структура

// Целевой интерфейс, который ожидают клиенты
abstract class JsonData {
  String getJson();
}

// Существующий класс с несовместимым интерфейсом
class XmlService {
  String getXml() {
    return '<data><name>John</name></data>';
  }
}

// Адаптер делает XmlService совместимым с JsonData
class XmlToJsonAdapter implements JsonData {
  final XmlService _xmlService;

  XmlToJsonAdapter(this._xmlService);

  @override
  String getJson() {
    String xml = _xmlService.getXml();
    // Преобразование XML в JSON
    return '{"name": "John"}';
  }
}

Использование

void main() {
  var xmlService = XmlService();
  var adapter = XmlToJsonAdapter(xmlService);

  print(adapter.getJson());  // {"name": "John"}
}

Адаптер реализует целевой интерфейс, оборачивая при этом несовместимый класс. Клиентский код работает со знакомым интерфейсом, не зная о внутренней реализации.

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

Попробуйте сами

import 'media_adapter.dart';

void main() {
  // TODO: Создайте VinylPlayer с названием трека "Bohemian Rhapsody"
  
  // TODO: Создайте VinylAdapter, который оборачивает vinyl player
  
  // TODO: Сохраните адаптер в переменную типа AudioSource
  
  // TODO: Вызовите playAudio() у вашего аудиоисточника и выведите результат
  
}
quiz iconПроверьте себя

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

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