Когда стоит использовать паттерн Adapter?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерн 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 когда:
-
Работаешь с legacy кодом
- Нельзя изменить старый класс
- Нужно интегрировать его с новой системой
-
Используешь external библиотеки
- API библиотеки не совпадает с твоим интерфейсом
- Хочешь изолировать зависимость от этой библиотеки
-
Необходима абстракция
- Скрыть реализацию 3rd-party кода
- Сделать легче тестирование через mocks
-
Множество похожих интерфейсов
- Несколько способов сделать одно и то же
- Нужен единый интерфейс для всех вариантов
-
Миграция кода
- Постепенно переходишь с одной системы на другую
- 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 библиотеками. Используй его для изоляции несовместимых интерфейсов, но не переусложняй архитектуру чрезмерным количеством адаптеров.