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

Приведи пример шаблона проектирования Adapter

1.8 Middle🔥 141 комментариев
#SOLID и паттерны проектирования

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

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

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

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

Adapter — структурный паттерн, который позволяет объектам с несовместимыми интерфейсами работать вместе. Преобразует интерфейс одного класса в другой интерфейс, который нужен клиенту.

Идея

Тебе нужно использовать класс, но его интерфейс не подходит.
Адаптер — это переходник, который преобразует интерфейс.

Аналогия: адаптер для зарядки (розетка USA → EU)

Пример 1: Интеграция старой БД с новым кодом

Старый код (Legacy)

// Старая БД из 2010-х, имеет странный интерфейс
public class LegacyDatabase {
    public String[] getUsersAsArray() {
        // Возвращает массив строк вместо объектов
        return new String[]{
            "John,john@example.com",
            "Jane,jane@example.com"
        };
    }
}

Новый код ожидает другой интерфейс

// Новый интерфейс, который мы хотим использовать
public interface UserRepository {
    List<User> getAllUsers();
    User getUserById(Long id);
    void saveUser(User user);
}

public class User {
    private Long id;
    private String name;
    private String email;
    
    // конструктор, getters, setters
}

Adapter решает проблему

// Адаптер преобразует LegacyDatabase в UserRepository
public class LegacyDatabaseAdapter implements UserRepository {
    private final LegacyDatabase legacyDB;
    
    public LegacyDatabaseAdapter(LegacyDatabase legacyDB) {
        this.legacyDB = legacyDB;
    }
    
    @Override
    public List<User> getAllUsers() {
        // Преобразуем старый формат в новый
        String[] rawData = legacyDB.getUsersAsArray();
        List<User> users = new ArrayList<>();
        
        for (String line : rawData) {
            String[] parts = line.split(",");
            User user = new User();
            user.setName(parts[0]);
            user.setEmail(parts[1]);
            users.add(user);
        }
        
        return users;
    }
    
    @Override
    public User getUserById(Long id) {
        // Старая БД не поддерживает поиск по ID
        // Или реализуем обходной путь
        return getAllUsers().stream()
            .filter(u -> u.getId().equals(id))
            .findFirst()
            .orElse(null);
    }
    
    @Override
    public void saveUser(User user) {
        // Старая БД не поддерживает сохранение
        // Бросаем исключение или логируем
        throw new UnsupportedOperationException(
            "Legacy DB не поддерживает сохранение"
        );
    }
}

Использование

public class UserService {
    private final UserRepository repository;
    
    // Сервис ничего не знает о LegacyDatabase
    // Он работает с UserRepository
    public UserService(UserRepository repository) {
        this.repository = repository;
    }
    
    public void printAllUsers() {
        List<User> users = repository.getAllUsers();
        for (User user : users) {
            System.out.println(user.getName() + " " + user.getEmail());
        }
    }
}

// Использование:
public static void main(String[] args) {
    LegacyDatabase legacyDB = new LegacyDatabase();
    UserRepository adapted = new LegacyDatabaseAdapter(legacyDB);
    UserService service = new UserService(adapted);
    
    service.printAllUsers();
    // Output:
    // John john@example.com
    // Jane jane@example.com
}

Пример 2: Адаптирование списков

Проблема

У тебя есть List<String> (names), а тебе нужно List<User> (users).

Adapter

// Адаптер преобразует List<String> в List<User>
public class StringToUserListAdapter extends AbstractList<User> {
    private final List<String> stringList;
    
    public StringToUserListAdapter(List<String> stringList) {
        this.stringList = stringList;
    }
    
    @Override
    public User get(int index) {
        String name = stringList.get(index);
        User user = new User();
        user.setName(name);
        return user;
    }
    
    @Override
    public int size() {
        return stringList.size();
    }
}

// Использование
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<User> users = new StringToUserListAdapter(names);

for (User user : users) {
    System.out.println(user.getName());
}

Пример 3: Адаптирование платёжных систем

Новый интерфейс

public interface PaymentProcessor {
    boolean processPayment(BigDecimal amount, String currency);
}

Старые платежи системы (разные интерфейсы)

// Stripe API (старый, свой интерфейс)
public class StripeGateway {
    public StripeResult charge(int amountInCents, String currency) {
        // Stripe работает в центах, требует int
        return new StripeResult(/* ... */);
    }
}

// PayPal API (другой интерфейс)
public class PayPalGateway {
    public PayPalResponse executePayment(double amount, String curr) throws PayPalException {
        // PayPal работает с double, выбрасывает exception
        return new PayPalResponse(/* ... */);
    }
}

Адаптеры

// Adapter для Stripe
public class StripeAdapter implements PaymentProcessor {
    private final StripeGateway stripe = new StripeGateway();
    
    @Override
    public boolean processPayment(BigDecimal amount, String currency) {
        int amountInCents = amount.multiply(new BigDecimal("100")).intValue();
        StripeResult result = stripe.charge(amountInCents, currency);
        return result.isSuccessful();
    }
}

// Adapter для PayPal
public class PayPalAdapter implements PaymentProcessor {
    private final PayPalGateway paypal = new PayPalGateway();
    
    @Override
    public boolean processPayment(BigDecimal amount, String currency) {
        try {
            PayPalResponse response = paypal.executePayment(
                amount.doubleValue(),
                currency
            );
            return response.isSuccessful();
        } catch (PayPalException e) {
            e.printStackTrace();
            return false;
        }
    }
}

Использование

public class CheckoutService {
    private final PaymentProcessor processor;
    
    public CheckoutService(PaymentProcessor processor) {
        this.processor = processor;
    }
    
    public void checkout(BigDecimal amount) {
        boolean success = processor.processPayment(amount, "USD");
        if (success) {
            System.out.println("Payment successful!");
        } else {
            System.out.println("Payment failed!");
        }
    }
}

// Использование
public static void main(String[] args) {
    // С Stripe
    CheckoutService stripeCheckout = new CheckoutService(
        new StripeAdapter()
    );
    stripeCheckout.checkout(new BigDecimal("99.99"));
    
    // С PayPal (просто меняем адаптер!)
    CheckoutService paypalCheckout = new CheckoutService(
        new PayPalAdapter()
    );
    paypalCheckout.checkout(new BigDecimal("99.99"));
}

Пример 4: Java Collections Adapter (реальный пример)

Java сама использует адаптеры! Например, Arrays.asList():

String[] array = {"a", "b", "c"};

// Arrays.asList() — это адаптер, преобразует массив в список
List<String> list = Arrays.asList(array);

// Теперь массив выглядит как List для кода, который ожидает List
for (String item : list) {
    System.out.println(item);
}

Внутри он выглядит примерно так:

private static class ArrayListAdapter<T> extends AbstractList<T> {
    private final T[] array;
    
    ArrayListAdapter(T[] array) {
        this.array = array;
    }
    
    @Override
    public T get(int index) {
        return array[index];
    }
    
    @Override
    public int size() {
        return array.length;
    }
}

Типы Adapter

1. Class Adapter (наследование)

public class LegacyDatabaseAdapter extends LegacyDatabase implements UserRepository {
    // Наследуем от старого класса
    @Override
    public List<User> getAllUsers() {
        // Используем методы родителя
        String[] data = getUsersAsArray();
        // ...
    }
}

Минус: Java — single inheritance, не всегда возможно

2. Object Adapter (композиция) ← РЕКОМЕНДУЕТСЯ

public class LegacyDatabaseAdapter implements UserRepository {
    private final LegacyDatabase legacyDB; // Композиция
    
    public LegacyDatabaseAdapter(LegacyDatabase legacyDB) {
        this.legacyDB = legacyDB;
    }
    // ...
}

Плюс: Гибче, более чистое дизайна

Плюсы и минусы

Плюсы

✅ Позволяет использовать несовместимые интерфейсы ✅ Не меняешь исходный код ✅ Слабое связывание (loose coupling) ✅ Single Responsibility — адаптер только адаптирует

Минусы

❌ Добавляет уровень абстракции ❌ Может снизить производительность (дополнительные вызовы) ❌ Если много адаптеров — сложность растёт

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

✅ Интеграция со старым кодом (legacy) ✅ Работа с внешними библиотеками с несовместимым API ✅ Адаптирование платёжных шлюзов, API провайдеров ✅ Преобразование разных форматов (JSON → XML, List → Array) ✅ Декораторы и middleware

Итог

Adapter — мост между несовместимыми интерфейсами. Это спасает, когда:

  • Нельзя менять исходный код
  • Нужно поддерживать legacy системы
  • Работаешь с разными external APIs

Лучше использовать Object Adapter (композиция) вместо Class Adapter (наследования).