Что такое паттерн Адаптер в программировании?

Ответ

Паттерн Адаптер (Adapter) — это структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе. Он выступает в роли обертки, преобразуя интерфейс одного класса (адаптируемого объекта, или adaptee) в интерфейс, ожидаемый клиентом (целевой интерфейс, или target).

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

Типы Адаптеров:

  1. Адаптер объекта (Object Adapter): Использует композицию. Адаптер содержит экземпляр адаптируемого объекта и делегирует ему вызовы, преобразуя их. Это наиболее распространенный подход в Python.
  2. Адаптер класса (Class Adapter): Использует множественное наследование (если язык поддерживает). Адаптер наследует как от целевого интерфейса, так и от адаптируемого класса. В Python менее применим из-за особенностей наследования и предпочтения композиции.

Пример (Object Adapter на Python): Представим, что у нас есть старая система логирования, которая принимает сообщения только через метод log_message_old_format, а новая система ожидает метод log_message.

# Адаптируемый класс (Adaptee) - старая система
class OldLogger:
    def log_message_old_format(self, message: str):
        print(f"[OLD LOG]: {message}")

# Целевой интерфейс (Target) - что ожидает клиент
class NewLoggerInterface:
    def log_message(self, message: str):
        raise NotImplementedError("Должен быть реализован подклассом")

# Адаптер (Adapter)
class LoggerAdapter(NewLoggerInterface):
    def __init__(self, old_logger: OldLogger):
        self._old_logger = old_logger

    def log_message(self, message: str):
        # Преобразуем вызов нового интерфейса в вызов старого
        self._old_logger.log_message_old_format(f"[ADAPTED] {message}")

# Клиентский код, который работает с новым интерфейсом
def client_code(logger: NewLoggerInterface, text: str):
    logger.log_message(text)

# Использование:
old_logger_instance = OldLogger()

# Клиент не может напрямую использовать OldLogger:
# client_code(old_logger_instance, "Это сообщение не сработает") # Ошибка атрибута

# Используем адаптер для совместимости
adapter = LoggerAdapter(old_logger_instance)
client_code(adapter, "Сообщение через адаптер")

# Вывод:
# [OLD LOG]: [ADAPTED] Сообщение через адаптер

Когда использовать паттерн Адаптер?

  • Интеграция: Когда нужно использовать существующий класс, но его интерфейс не соответствует потребностям.
  • Сторонние библиотеки: При работе с библиотеками, которые имеют полезную функциональность, но неудобный или несовместимый API.
  • Унификация: Для предоставления единого интерфейса к нескольким классам с похожей, но не идентичной функциональностью.

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

  • Повторное использование кода: Позволяет использовать существующие классы без их модификации.
  • Разделение ответственности: Отделяет логику преобразования интерфейсов от основной бизнес-логики.
  • Гибкость: Позволяет легко заменять адаптируемые классы, не затрагивая клиентский код.

Недостатки:

  • Увеличение сложности: Добавляет новые классы, что может усложнить структуру проекта.
  • Производительность: Небольшие накладные расходы из-за дополнительного уровня абстракции (обычно незначительные).

Ответ 18+ 🔞

А, паттерн Адаптер, говоришь? Ну это ж классика, блядь, ёпта! Представь себе ситуацию, ну прям как в жизни, сука.

Вот есть у тебя, допустим, старый, блядь, советский штепсель — этакий здоровый, с толстыми ногами. А розетка у тебя новая, европейская, с этими, блядь, дырочками тоненькими. И ты такой: "Ну какого хуя?" Ты ж не будешь стену ломать, розетку менять или, того хуже, вилку зубилом обтачивать, блядь! Ты берёшь переходничок, эту самую, сука, прокладку между мирами — и вот он, твой Адаптер, нахуй! Вставил старую вилку в одну сторону, а другую — в новую розетку, и свет загорелся, пиздец!

Так и в коде, блядь. Есть у тебя какая-нибудь старая библиотека, написанная ещё когда трава была зеленее, с методами типа log_message_old_format(). А ты уже всю новую систему построил вокруг красивого интерфейса NewLoggerInterface.log_message(). И что, переписывать старую библиотеку, на которую завязано пол-проекта? Да хуй там! Напрягись, жопа с ручками, и сделай Адаптер!

Смотри, как это выглядит, только не пугайся, код оставляю как есть, он правильный:

# Это наш старый дед, Adaptee. Говорит на своём языке.
class OldLogger:
    def log_message_old_format(self, message: str):
        print(f"[OLD LOG]: {message}")

# А это то, на чём говорит весь наш новый шикарный район, Target.
class NewLoggerInterface:
    def log_message(self, message: str):
        raise NotImplementedError("Должен быть реализован подклассом")

# А вот и наш переводчик-приспособленец, сам Адаптер!
class LoggerAdapter(NewLoggerInterface):
    def __init__(self, old_logger: OldLogger):
        self._old_logger = old_logger  # Засунули старика в карман

    def log_message(self, message: str):
        # А тут магия: берём новый вызов и конвертим в старый.
        self._old_logger.log_message_old_format(f"[ADAPTED] {message}")

# Клиентский код — он избалованный, общается только на новом языке.
def client_code(logger: NewLoggerInterface, text: str):
    logger.log_message(text)

# Поехали, сука!
old_logger_instance = OldLogger()

# Прямой вызов — облом. Клиент тупо не поймёт старика.
# client_code(old_logger_instance, "Не сработает") # Вылетит с ошибкой, как пробка!

# А вот так — красота! Даём клиенту нашего переводчика.
adapter = LoggerAdapter(old_logger_instance)
client_code(adapter, "Сообщение через адаптер")

# На выходе получим:
# [OLD LOG]: [ADAPTED] Сообщение через адаптер

Видишь? Клиенту похуй, кто там внутри — старый пень или нейросеть. Ему главное, чтобы метод log_message был. А адаптер, этот хитрожопый посредник, берёт вызов, немножко его облизывает, подготавливает и суёт старику, который делает свою работу. Всё довольны, мир во всём мире, ебать мои старые костыли!

Когда это впихивать в проект? Да когда угодно, блядь! Когда подключаешь левую библиотеку, от которой воняет нафталином. Когда два модуля друг на друга криво смотрят и не могут договориться. Когда нужно сделать единую рожу для кучи разных классов, которые делают одно и то же, но называют это разными словами.

Плюсы, минусы? Плюс — не надо ломать работающий код, можно повторно использовать старый хлам. Минус — появляется ещё один слой, эти ваши абстракции, за которыми надо следить. Но это, как говорится, овердохуища лучше, чем перелопачивать тонны legacy-кода. Короче, паттерн как таблетка от головной боли при интеграции. Выпей — и живи спокойно.