Адаптер (Adapter)

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

Сигналы применения

  • полезный класс нельзя или неразумно изменять;
  • сторонний либо старый API использует другие типы, единицы или имена методов;
  • доменная часть не должна зависеть от SDK внешней системы;
  • миграция идёт постепенно, и старый интерфейс некоторое время живёт рядом с новым.

Если требуется добавить поведение при том же интерфейсе, чаще нужен Декоратор Decorator, а не Adapter.

Роли и взаимодействие

  • Target — контракт, понятный клиенту;
  • Adaptee — существующий несовместимый объект;
  • Adapter — реализует Target и делегирует Adaptee с преобразованием;
  • Client — работает только с Target.

Адаптер объекта хранит ссылку на adaptee. Вариант через наследование жёстче, не работает с final-классами и в Java ограничен одиночным наследованием.

Пример на Java

interface TemperatureSource { double celsius(); }
final class LegacyGauge {
    int readFahrenheitTimesTen() { return 734; } // 73,4 °F
}
final class GaugeAdapter implements TemperatureSource {
    private final LegacyGauge gauge;
    GaugeAdapter(LegacyGauge gauge) { this.gauge = gauge; }
    @Override public double celsius() {
        double fahrenheit = gauge.readFahrenheitTimesTen() / 10.0;
        return (fahrenheit - 32.0) * 5.0 / 9.0;
    }
}
TemperatureSource source = new GaugeAdapter(new LegacyGauge());
System.out.printf("%.1f%n", source.celsius());

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

Где проходит граница

Хороший адаптер защищает внутреннюю модель от внешних DTO, исключений и соглашений. Он может сопоставить типы ошибок, однако не должен превращаться в слой со сложными бизнес-правилами. Такие правила принадлежат сервису предметной области.

Компромиссы

  • несовместимость локализована в одном месте;
  • тестировать внутренний код можно через Target без реального SDK;
  • появляется дополнительный слой и преобразования;
  • «универсальный» адаптер быстро разрастается при различиях семантики;
  • потеря данных при преобразовании должна быть явно описана и протестирована.

Чем отличается

  • Декоратор Decorator сохраняет контракт и добавляет обязанности;
  • Заместитель Proxy сохраняет контракт и контролирует доступ к объекту;
  • Фасад Facade упрощает работу с целой подсистемой, а Adapter согласует два конкретных контракта;
  • Bridge заранее разделяет две изменяемые иерархии, Adapter обычно соединяет уже существующие типы.

Вопросы на собеседовании

  1. Меняет ли Adapter бизнес-смысл? Нет, он переводит представление одного совместимого смысла; существенная новая логика — тревожный сигнал.
  2. Можно ли адаптировать исключения? Да, внешний тип ошибки часто переводят во внутренний, сохраняя причину.
  3. Что тестировать? Преобразование полей, единиц, null, границ и ошибок.
  4. Двусторонний адаптер хорош? Иногда нужен, но сложнее: оба контракта могут противоречить друг другу.
  5. Почему композиция предпочтительнее? Она слабее связывает типы и позволяет подменять adaptee.

Связи: Структурные паттерны, Принципы SOLID, Шаблоны проектирования.