Lapa Knowledge Base
Структурные паттерны

Adapter

Паттерн Adapter

Adapter позволяет взаимодействовать двум изначально несовместимым интерефейсам программы. Его главная задача - реализация нужного интерфейса и передача его вызовов адаптируемому объекту. По этой причине Адаптер часто называют "переходником" или "оберткой".

Является паттерном структурного типа.


Для чего используется?

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

Пример - приложение может использовать похожие, но различные API для реализации финансовых расчетов - калькулятор налогов, анализ денежных потоков и т.д. Функционал данных модулей реализуется через сторонние библиотеки, код которых нельзя модифицировать. Изменения в функционал этих API можно внести в рамках объекта-адаптера, в то время как приложение будет использовать только свой "родной" интерфейс.

Когда удобно применять:

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

Варианты кейсов использования:

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

Какова механика реализации?

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

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


Структура и отношения

Target
└── request(): string

Adaptee
└── specificRequest(): string

Adapter
├── adaptee: Adaptee
└── request(): string

Client
└── clientCode(target: Target): void

Пример реализации

// Целевой интерфейс (тот, который ожидает клиент)
class PaymentProcessor {
    processPayment(amount, currency) {
        throw new Error('Метод processPayment должен быть переопределен');
    }
}

// Существующий класс с несовместимым интерфейсом
class LegacyPaymentSystem {
    pay(amountInCents, currencyCode) {
        console.log(`Обработка платежа: ${amountInCents} центов в ${currencyCode}`);
        return {
            success: true,
            transactionId: `legacy_${Date.now()}`,
            amount: amountInCents,
            currency: currencyCode
        };
    }
}

// Адаптер
class LegacyPaymentAdapter extends PaymentProcessor {
    constructor(legacySystem) {
        super();
        this.legacySystem = legacySystem;
 }

    processPayment(amount, currency) {
        // Конвертируем доллары в центы
        const amountInCents = Math.round(amount * 100);
    
        // Конвертируем название валюты в код
        const currencyCode = this.getCurrencyCode(currency);
    
        // Вызываем метод старой системы
        const result = this.legacySystem.pay(amountInCents, currencyCode);
    
        // Возвращаем результат в новом формате
        return {
            success: result.success,
            transactionId: result.transactionId,
            amount: result.amount / 100, // Конвертируем обратно в доллары
            currency: this.getCurrencyName(result.currency)
        };
    }

    getCurrencyCode(currency) {
        const codes = {
            'USD': 'USD',
            'EUR': 'EUR',
            'RUB': 'RUB'
        };
        return codes[currency] || 'USD';
    }

    getCurrencyName(code) {
        const names = {
            'USD': 'USD',
            'EUR': 'EUR',
            'RUB': 'RUB'
        };
        return names[code] || 'USD';
    }
}

// Современная система платежей (совместима с целевым интерфейсом)
class ModernPaymentSystem extends PaymentProcessor {
    processPayment(amount, currency) {
        console.log(`Современная обработка платежа: ${amount} ${currency}`);
        return {
            success: true,
            transactionId: `modern_${Date.now()}`,
            amount: amount,
            currency: currency
        };
    }
}

// Клиентский код
function processPayment(processor, amount, currency) {
    const result = processor.processPayment(amount, currency);
    console.log(`Результат: ${JSON.stringify(result)}`);
    return result;
}

// Использование
const legacySystem = new LegacyPaymentSystem();
const legacyAdapter = new LegacyPaymentAdapter(legacySystem);
const modernSystem = new ModernPaymentSystem();

// Оба работают с одинаковым интерфейсом
processPayment(legacyAdapter, 100.50, 'USD');
processPayment(modernSystem, 100.50, 'USD');

Преимущества

  • Логика приложения остается сфокусированной - код конкретного функционала реализуется внутри адаптера;
  • Обеспечение поддержки различных интерфейсов и типов данных;
  • Позволяет использовать код разных приложений повторно.

Недостатки

  • Есть риск излишнего использования адаптеров для реализация мелкого функционала;
  • Введение дополнительных классов усложняет код программы.

Отличия от других паттернов

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

Реальный пример

// Прототип для создания пользовательских интерфейсов
class UIComponent {
    constructor(type, props = {}) {
        this.type = type;
        this.props = { ...props };
        this.children = [];
    }
    
    clone() {
        const cloned = new UIComponent(this.type, this.props);
        cloned.children = [...this.children];
        return cloned;
    }
    
    addChild(child) {
        this.children.push(child);
        return this;
    }
    
    render() {
        return `<${this.type} ${Object.entries(this.props).map(([k, v]) => `${k}="${v}"`).join(' ')}>${this.children.map(child => child.render()).join('')}</${this.type}>`;
    }
}

// Создаем прототипы компонентов
const buttonPrototype = new UIComponent('button', { class: 'btn', type: 'button' });
const inputPrototype = new UIComponent('input', { type: 'text', class: 'form-control' });
const divPrototype = new UIComponent('div', { class: 'container' });

// Создаем копии и настраиваем их
const submitButton = buttonPrototype.clone();
submitButton.props.value = 'Отправить';
submitButton.props.class = 'btn btn-primary';

const cancelButton = buttonPrototype.clone();
cancelButton.props.value = 'Отмена';
cancelButton.props.class = 'btn btn-secondary';

const form = divPrototype.clone();
form.props.class = 'form-group';

const nameInput = inputPrototype.clone();
nameInput.props.placeholder = 'Введите имя';
nameInput.props.name = 'name';

const emailInput = inputPrototype.clone();
emailInput.props.placeholder = 'Введите email';
emailInput.props.name = 'email';
emailInput.props.type = 'email';

// Собираем форму
form.addChild(nameInput);
form.addChild(emailInput);
form.addChild(submitButton);
form.addChild(cancelButton);

console.log(form.render());

Adapter — это мост между несовместимыми интерфейсами!

Copyright © 2026