Что такое паттерн Адаптер (Adapter)?

Ответ

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

Аналогия из жизни: Это переходник для розетки, который позволяет вилке одного стандарта работать с розеткой другого.

Когда использовать:

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

Реализация: Существует два основных типа адаптеров:

1. Адаптер на основе композиции (объектный адаптер) — предпочтительный способ.

// Целевой интерфейс, который ожидает клиентский код
public interface ITarget
{
    string GetRequest();
}

// Адаптируемый класс с «чужим» интерфейсом
public class Adaptee
{
    public string GetSpecificRequest()
    {
        return "Specific request from Adaptee.";
    }
}

// Адаптер: реализует целевой интерфейс и содержит ссылку на Adaptee
public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public string GetRequest()
    {
        // Преобразование вызова GetRequest() в вызов GetSpecificRequest()
        return _adaptee.GetSpecificRequest();
    }
}

// Клиентский код работает только с интерфейсом ITarget
class Client
{
    static void Main()
    {
        Adaptee adaptee = new Adaptee();
        ITarget target = new Adapter(adaptee); // Обёртывание

        Console.WriteLine(target.GetRequest()); // Вывод: Specific request from Adaptee.
    }
}

2. Адаптер на основе наследования (классовый адаптер) — требует множественного наследования (недоступно в C#, но возможно в C++).

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

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

Недостатки:

  • Усложняет код, добавляя новые классы. Иногда проще изменить клиентский код, если это возможно.

Ответ 18+ 🔞

Ну ты смотри, какой занятный паттерн, блядь! Адаптер, сука! Это ж как наш универсальный переходник для зарядки, который из трёх рогов в два превращает, чтобы в розетку воткнуть. Без него — пиздец, телефон сдохнет, а с ним — красота, работает.

Короче, зачем это нужно, ёпта? Представь: у тебя есть старый, блядь, принтер, который печатает только из своего особого софта, а у тебя программа новая, которая шлёт команды по-другому. Так вот, адаптер — это такой чувак-переводчик, который берёт команду от новой программы, говорит ей: «Да всё окей, братан», а сам на ухо принтеру шепчет: «Слушай сюда, старый пердун, печатай вот это, но по-нашему, по-старинному». И принтер, довольный, жужжит. Все счастливы, мир во всём мире.

Когда его впендюривать?

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

Как это выглядит в коде, на примере? Смотри, есть у нас целевой интерфейс — это то, что хочет наш клиент. Как розетка на 220В.

// Вот это наша родная, правильная розетка. Все её любят.
public interface IРозетка
{
    string ДатьТок();
}

А есть какой-то чудак-американский прибор с вилкой на 110В.

// Это какой-то залётный гаджет с неправильной вилкой.
public class АмериканскийЧайник
{
    public string GetPower()
    {
        return "110V power, dude!";
    }
}

Вот их просто так не состыкуешь, блядь. Щас или чайник спалишь, или пробки выбьет. Нужен переходник!

// А вот и наш герой — переходник-адаптер!
public class АдаптерДляЧайника : IРозетка // С одной стороны — наша родная розетка
{
    private readonly АмериканскийЧайник _чайник; // А внутри — приблуда иностранная

    public АдаптерДляЧайника(АмериканскийЧайник чайник)
    {
        _чайник = чайник; // Засунули чайник в адаптер
    }

    public string ДатьТок()
    {
        // А тут магия! Говорим "дать ток" по-нашему, а адаптер сам переводит на язык чайника.
        var странныйТок = _чайник.GetPower();
        return $"Конвертировал {странныйТок} в наши родные 220В. Кипятись, сука!";
    }
}

И теперь наш главный код, которому похуй на детали, просто тыкает в розетку.

class ЯХозяин
{
    static void Main()
    {
        // Вот этот чужой прибор
        АмериканскийЧайник чужак = new АмериканскийЧайник();
        // Обматываем его адаптером — и вуаля, он теперь как свой!
        IРозетка розетка = new АдаптерДляЧайника(чужак);

        // Втыкаем в сеть и не паримся!
        Console.WriteLine(розетка.ДатьТок());
        // Выведет: "Конвертировал 110V power, dude! в наши родные 220В. Кипятись, сука!"
    }
}

Что в этом хорошего, блядь?

  • Не трогаем старое: Этот американский чайник — он же священная корова, его код никто не смеет трогать. Адаптер обернул — и работает.
  • Всё отдельно: Логика преобразования сидит в адаптере, а не размазана по всей программе. Получился пиздопроебибна модуль.
  • Гибкость: Захотел подключить ещё немецкий утюг — сделал для него второй адаптер. Клиентский код даже не чихнёт.

Что плохого? А плохо то, что классов становится, блядь, как говна за баней. Каждую новую хрень нужно оборачивать. Иногда проще, ёпта, пнуть того, кто писал клиентский код, чтобы он поддержал новый интерфейс. Но если не вариант — то адаптер наш спаситель.

Короче, паттерн как таблетка от головной боли, когда системы стыковать нужно, а они как бабы на базаре — говорят на разных языках.