← Назад к вопросам

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

1.0 Junior🔥 171 комментариев
#STL контейнеры и алгоритмы#Исключения и обработка ошибок

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Паттерн Adapter (Адаптер)

Паттерн Adapter — это структурный паттерн проектирования, который позволяет объектам с несовместимыми интерфейсами работать вместе. Рассмотрим случаи его применения в C/C++ backend разработке.

Что такое Adapter

Это промежуточный объект (wrapper), который преобразует интерфейс одного класса в интерфейс другого, который ожидает клиент. Паттерн особенно полезен, когда:

  • Нельзя изменить исходный класс (legacy код, external library)
  • Нужно использовать класс с несовместимым интерфейсом
  • Требуется несколько похожих, но разных интерфейсов

Пример 1: Интеграция с Legacy API

Старый, но критичный код использует свой интерфейс обработки платежей. Новая система требует другой интерфейс:

// Старый интерфейс (legacy код, который менять нельзя)
class OldPaymentGateway {
public:
    bool ProcessPayment(const std::string& account, double amount) {
        std::cout << "Processing via OLD gateway: " << amount << std::endl;
        return true;
    }
};

// Новый интерфейс, который требует система
class IPaymentProcessor {
public:
    virtual ~IPaymentProcessor() = default;
    virtual bool Pay(const std::string& id, double sum) = 0;
};

// Adapter преобразует старый интерфейс в новый
class PaymentAdapter : public IPaymentProcessor {
private:
    OldPaymentGateway& gateway;
    
public:
    PaymentAdapter(OldPaymentGateway& gw) : gateway(gw) {}
    
    bool Pay(const std::string& id, double sum) override {
        // Преобразуем параметры: id -> account, sum -> amount
        return gateway.ProcessPayment(id, sum);
    }
};

int main() {
    OldPaymentGateway oldGateway;
    PaymentAdapter adapter(oldGateway);
    
    // Теперь можем использовать старый код через новый интерфейс
    adapter.Pay("acc123", 100.50);
    return 0;
}

Пример 2: Работа с несовместимыми библиотеками

Представь, что у тебя два способа логирования — старый и новый:

// Библиотека 1: старая система логирования
class LegacyLogger {
public:
    void log_message(const char* msg) {
        std::cout << "[OLD LOG] " << msg << std::endl;
    }
};

// Библиотека 2: новая система требует этот интерфейс
class ILogger {
public:
    virtual ~ILogger() = default;
    virtual void Write(const std::string& message) = 0;
};

// Adapter
class LoggerAdapter : public ILogger {
private:
    LegacyLogger& logger;
    
public:
    LoggerAdapter(LegacyLogger& l) : logger(l) {}
    
    void Write(const std::string& message) override {
        logger.log_message(message.c_str());
    }
};

// Использование
int main() {
    LegacyLogger oldLogger;
    LoggerAdapter adapter(oldLogger);
    
    adapter.Write("Hello from new logger!");  // Использует старый формат
    return 0;
}

Пример 3: Двусторонний Adapter (Two-way Adapter)

Иногда нужно адаптировать оба направления:

class DatabaseA {
public:
    void connect_db(const std::string& host, int port) {
        std::cout << "Connected to DB A" << std::endl;
    }
    void query(const std::string& sql) {
        std::cout << "Executing: " << sql << std::endl;
    }
};

class DatabaseB {
public:
    virtual ~DatabaseB() = default;
    virtual bool Connect(const std::string& connection_string) = 0;
    virtual void Execute(const std::string& statement) = 0;
};

// Двусторонний адаптер
class DatabaseAdapter : public DatabaseB {
private:
    DatabaseA db;
    
public:
    bool Connect(const std::string& connection_string) override {
        // Парсим строку подключения
        db.connect_db("localhost", 5432);
        return true;
    }
    
    void Execute(const std::string& statement) override {
        db.query(statement);
    }
};

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

✅ Используй Adapter когда:

  1. Работаешь с legacy кодом

    • Нельзя изменить старый класс
    • Нужно интегрировать его с новой системой
  2. Используешь external библиотеки

    • API библиотеки не совпадает с твоим интерфейсом
    • Хочешь изолировать зависимость от этой библиотеки
  3. Необходима абстракция

    • Скрыть реализацию 3rd-party кода
    • Сделать легче тестирование через mocks
  4. Множество похожих интерфейсов

    • Несколько способов сделать одно и то же
    • Нужен единый интерфейс для всех вариантов
  5. Миграция кода

    • Постепенно переходишь с одной системы на другую
    • Adapter позволяет работать обеим одновременно

Пример 4: Adapter для работы с протоколами

// Старый protobuf интерфейс
class ProtobufMessage {
public:
    std::string Serialize() { return "protobuf_data"; }
};

// Новый JSON интерфейс
class ISerializer {
public:
    virtual ~ISerializer() = default;
    virtual std::string Serialize(const void* data) = 0;
};

// Adapter
class ProtobufToJsonAdapter : public ISerializer {
private:
    ProtobufMessage msg;
    
public:
    std::string Serialize(const void* data) override {
        // Конвертируем protobuf в JSON
        auto pb_data = msg.Serialize();
        return "{\"data\": \"" + pb_data + "\"}";  // Упрощённо
    }
};

Когда НЕ использовать Adapter

❌ Избегай Adapter когда:

  • Можешь просто переписать класс
  • Добавляет ненужную сложность
  • Используется слишком много адаптеров (признак плохого дизайна)
  • Адаптер становится толще исходного класса

Преимущества и недостатки

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

  • Следует принципу Open/Closed (открыт для расширения, закрыт для модификации)
  • Изолирует изменения 3rd-party кода
  • Упрощает тестирование
  • Декраширует легаси код

Недостатки:

  • Добавляет дополнительный уровень абстракции
  • Может замаскировать плохой дизайн
  • Снижает производительность (вызов extra функции)
  • Может привести к излишней сложности, если переиспользуется

Вывод

В C/C++ backend разработке Adapter — необходимый паттерн для работы с legacy кодом и external библиотеками. Используй его для изоляции несовместимых интерфейсов, но не переусложняй архитектуру чрезмерным количеством адаптеров.