Также известен как Обёртка, Adapter

Адаптер

Суть паттерна

Обеспечивает совместную работу классов с несовместимыми интерфейсами.

Проблема

К вашему приложению, работающему с данными в XML, нужно прикрутить стороннюю библиотеку, работающую в JSON.

Например, ваше приложение скачивает биржевые котировки из нескольких источников в XML и рисует красивые графики. В какой-то момент вы решаете улучшить приложение, применив стороннюю библиотеку аналитики. Но вот беда, библиотека поддерживает только JSON формат данных.

Проблема паттерна Адаптер

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

Решение

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

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

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

Решение паттерна Адаптер

Таким образом, в приложении биржевых котировок, вы могли бы создать класс XML_To_JSON_Adapter, который бы оборачивал класс библиотеки аналитики. Ваш код посылал бы запросы этому объекту в XML, а адаптер бы сначала транслировал входящие данные в JSON, а затем передавал бы их определённым методам библиотеки.

Аналогия из жизни

Розетки в разных странах

Если вы летите из Штатов в Европу вас ждёт сюрприз при попытке зарядить ноутбук. Стандарт розеток в разных странах может не совпадать и обычная штатовская вилка не подойдёт в Германии.

Проблему решает адаптер, который содержит американскую розетку и немецкую вилку.

Структура

Адаптер объектов

Эта реализация использует принцип композиции — то есть объект адаптера содержит ссылку на служебный объект. Работает в большинстве языков программирования.

Схема структуры классов паттерна Адаптер (адаптер объектов)

Адаптер классов

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

Схема структуры классов паттерна Адаптер (адаптер объектов)
  1. Существующий интерфейс или класс уже работающий и совместимый с основным кодом программы.

  2. Сервис – это какой-то полезный класс, обычно сторонний, нуждающийся в адаптации для Клиента.

  3. Адаптер реализует Клиентский интерфейс и содержит ссылку на объект Служебного класса.

    Адаптер получает вызовы от Клиента через методы Клиентского интерфейса, а затем конвертирует их в вызовы методов обёрнутого объекта в правильном формате.

  4. Клиент использует Адаптер через Существующий интерфейс.

    Это позволяет добавлять в программу новые классы Адаптеров независимо от клиентского кода (это нужно если интерфейс Сервиса вдруг изменится, например после выхода новой версии сторонней библиотеки).

  5. Адаптер классов не нуждается во вложенном объекте, так как он одновременно наследует и существующий и сервисный интерфейс.

Псевдокод

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

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

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

// Классы с совместимыми интерфейсами: КруглоеОтверстие и КруглыйКолышек.
class RoundHole is
    constructor RoundHole(radius) { ... }
    method getRadius
    method fits(peg: RoundPeg) is
        return this.getRadius() >= peg.radius()

class RoundPeg is
    constructor RoundPeg(radius) { ... }

    method getRadius() is
        Return the peg radius.


// Устаревший несовместимый класс: КвадратныйКолышек.
class SquarePeg is
    constructor SquarePeg(width) { ... }

    method getWidth() is
        Return the square peg width.


// Адаптер позволяет использовать квадратные колышки и круглые отверстия вместе.
class SquarePegAdapter extends RoundPeg is
    field peg: SquarePeg

    constructor SquarePegAdapter(peg: SquarePeg) is
        this.peg = peg

    method getRadius() is
        return Math.sqrt(Math.pow((peg.getWidth()/2), 2) * 2);


// Где-то в клиентском коде.
hole = new RoundHole(5)
rpeg = new RoundPeg(5)
hole.fits(rpeg) // true

small_sqpeg = new SquarePeg(2)
large_sqpeg = new SquarePeg(5)
hole.fits(small_sqpeg) // не будет компилироваться из-за ошибки типов

small_sqpeg_adapter = new SquarePegAdapter(small_sqpeg)
large_sqpeg_adapter = new SquarePegAdapter(large_sqpeg)
hole.fits(small_sqpeg_adapter) // true
hole.fits(large_sqpeg_adapter) // false

Применимость

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

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

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

Вы могли бы создать ещё один уровень подклассов, и добавить в них недостающую функциональность. Но при этом придётся дублировать один и тот же код в обоих ветках подклассов.

Более элегантное решение — поместить недостающую функциональность в адаптер и приспособить его для работы с суперклассом. Такой адаптер сможет работать со всеми подклассами иерархии. Это решение будет сильно напоминать паттерн Посетитель.

Шаги реализации

  1. Убедитесь, что у вас есть два класса с неудобными интерфейсами:

    • полезный служебный класс, который вы не можете изменять (он либо сторонний, либо от него зависит другой код);
    • один или несколько классов приложения, несовместимых с этим служебным классом из-за неудобного или несовпадающего интерфейса.
  2. Опишите клиентский интерфейс, через который классы приложения смогли бы использовать сторонний класс.

  3. Создайте класс адаптера, реализовав этот интерфейс.

  4. Поместите в адаптер поле-ссылку на объект служебного класса. В большинстве случае, это это поля заполняется объектом переданным в констурктор адаптера. В случае простой адаптации этот объект можно передавать через параметры методов адаптера.

  5. Реализуйте все методы клиентского интерфейса в адаптере. Адаптер должен делегировать основную работу стороннему объекту.

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

Преимущества и недостатки

  • Отделяет и скрывает от клиента подробности преобразования различных интерфейсов.
  • Усложняет код программы за счёт дополнительных классов.

Отношения с другими паттернами

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

  • Адаптер предоставляет классу альтернативный интерфейс. Заместитель предоставляет тот же интерфейс. Декоратор предоставляет расширенный интерфейс.

  • Адаптер меняет интерфейс существующего объекта. Декоратор улучшает другой объект без изменения его интерфейса. Причём Декоратор поддерживает рекурсивную вложенность, чего не скажешь об Адаптере.

  • Фасад задаёт новый интерфейс, тогда как Адаптер повторно использует старый. Адаптер оборачивает только один класс, а Фасад оборачивает целую подсистему. Кроме того, Адаптер позволяет двум существующим интерфейсам работать сообща, вместо того, чтобы задать полностью новый.

  • Мост, Стратегия и Состояние (а также слегка и Адаптер) имеют схожие структуры классов — все они построены на принципе «композиции», то есть делегирования работы другим объектам. Тем не менее они отличаются тем, что решают разные проблемы. Помните, что паттерны — это не только рецепт построения кода определённым образом, но и описание проблем, которые привели к данному решению.

Реализация в различных языках программирования

Java