Адаптер (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 обычно соединяет уже существующие типы.
Вопросы на собеседовании
- Меняет ли Adapter бизнес-смысл? Нет, он переводит представление одного совместимого смысла; существенная новая логика — тревожный сигнал.
- Можно ли адаптировать исключения? Да, внешний тип ошибки часто переводят во внутренний, сохраняя причину.
- Что тестировать? Преобразование полей, единиц,
null, границ и ошибок. - Двусторонний адаптер хорош? Иногда нужен, но сложнее: оба контракта могут противоречить друг другу.
- Почему композиция предпочтительнее? Она слабее связывает типы и позволяет подменять adaptee.
Связи: Структурные паттерны, Принципы SOLID, Шаблоны проектирования.