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 и делает две вещи: переименовывает метод (pay → make_payment) и преобразует данные (рубли → центы). Завтра подключим PayPal — напишем PayPalAdapter, остальной код не тронем.
Объектный и классовый адаптер
Показанный вариант — объектный адаптер: он держит чужой объект внутри (композиция). Есть и классовый через множественное наследование, но композиция гибче и в Python предпочтительнее.
Адаптер делает ровно две вещи и не больше: переименовывает методы и преобразует данные между форматами. Как только в обёртку хочется добавить «ещё немного логики» — кеш, проверку, накопление состояния — это сигнал, что вам нужен уже не Adapter, а Decorator или Proxy. Держите адаптер тонким: чем меньше он делает, тем проще убедиться, что он лишь честно переводит вызовы.
Отдельная польза адаптера — изоляция от чужого API. Если завтра вы решите сменить платёжный шлюз, весь «контакт» с внешним миром сосредоточен в одном классе-адаптере. Остальной код, написанный против вашего интерфейса PaymentProcessor, переписывать не придётся. Это снижает стоимость замены зависимостей и делает их подменяемыми в тестах на заглушки.
Плюсы и минусы
- Плюс: переиспользуете чужой код, не меняя ни его, ни своего клиента; соблюдаете OCP.
- Минус: ещё один класс-прослойка; при большом количестве адаптеров растёт число обёрток.
Где встречается
Обёртки над платёжными системами, унификация разных HTTP-клиентов, мост между старым и новым API при миграции, ORM поверх разных драйверов БД. В стандартной библиотеке io.TextIOWrapper адаптирует байтовый поток к текстовому.
Итог
- Adapter переводит один интерфейс в другой, не меняя адаптируемый класс.
- Чаще всего реализуется композицией (объектный адаптер).
- Идеален для обёртки сторонних библиотек и legacy-кода.