Adapter

Паттерн-переходник: заставляет работать вместе классы с несовместимыми интерфейсами, не меняя их код.

Adapter оборачивает объект с одним интерфейсом так, чтобы он выглядел как объект с другим, ожидаемым интерфейсом.

Какую задачу решает

У вас есть код, который ждёт метод pay(amount). А подключаемая сторонняя библиотека предлагает make_payment(sum_in_cents). Менять чужой код нельзя, переписывать свой — дорого. Adapter — это тонкая обёртка-переходник, которая переводит ваши вызовы в вызовы чужого API.

Идея и реализация

class StripeApi:                 # чужой класс с «неудобным» интерфейсом
    def make_payment(self, cents):
        print(f"Stripe списал {cents} центов")


class PaymentProcessor:          # интерфейс, который ждёт наш код
    def pay(self, rubles):
        raise NotImplementedError


class StripeAdapter(PaymentProcessor):
    def __init__(self, stripe):
        self._stripe = stripe

    def pay(self, rubles):       # переводим рубли в центы и метод в метод
        self._stripe.make_payment(rubles * 100)


def checkout(processor: PaymentProcessor, amount):
    processor.pay(amount)


checkout(StripeAdapter(StripeApi()), 50)

Вывод:

Stripe списал 5000 центов

Наш checkout знает только интерфейс PaymentProcessor с методом pay. Адаптер прячет за собой StripeApi и делает две вещи: переименовывает метод (paymake_payment) и преобразует данные (рубли → центы). Завтра подключим PayPal — напишем PayPalAdapter, остальной код не тронем.

Объектный и классовый адаптер

Показанный вариант — объектный адаптер: он держит чужой объект внутри (композиция). Есть и классовый через множественное наследование, но композиция гибче и в Python предпочтительнее.

Адаптер делает ровно две вещи и не больше: переименовывает методы и преобразует данные между форматами. Как только в обёртку хочется добавить «ещё немного логики» — кеш, проверку, накопление состояния — это сигнал, что вам нужен уже не Adapter, а Decorator или Proxy. Держите адаптер тонким: чем меньше он делает, тем проще убедиться, что он лишь честно переводит вызовы.

Отдельная польза адаптера — изоляция от чужого API. Если завтра вы решите сменить платёжный шлюз, весь «контакт» с внешним миром сосредоточен в одном классе-адаптере. Остальной код, написанный против вашего интерфейса PaymentProcessor, переписывать не придётся. Это снижает стоимость замены зависимостей и делает их подменяемыми в тестах на заглушки.

Плюсы и минусы

  • Плюс: переиспользуете чужой код, не меняя ни его, ни своего клиента; соблюдаете OCP.
  • Минус: ещё один класс-прослойка; при большом количестве адаптеров растёт число обёрток.

Где встречается

Обёртки над платёжными системами, унификация разных HTTP-клиентов, мост между старым и новым API при миграции, ORM поверх разных драйверов БД. В стандартной библиотеке io.TextIOWrapper адаптирует байтовый поток к текстовому.

Итог

  • Adapter переводит один интерфейс в другой, не меняя адаптируемый класс.
  • Чаще всего реализуется композицией (объектный адаптер).
  • Идеален для обёртки сторонних библиотек и legacy-кода.
Проверьте себя
1. Что делает Adapter?
AСоздаёт один экземпляр класса
BПереводит вызовы одного интерфейса в вызовы другого
CКеширует результаты
DУведомляет подписчиков
2. Какой способ реализации адаптера в Python предпочтительнее?
AМножественное наследование (классовый адаптер)
BКомпозиция (объектный адаптер)
CГлобальные переменные
DМетаклассы
3. Для чего чаще всего применяют Adapter на практике?
AДля ускорения циклов
BДля обёртки сторонних библиотек и legacy-кода
CДля сортировки данных
DДля генерации случайных чисел
Поддержать проект