Приведи пример шаблона проектирования Adapter
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Паттерн 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 (наследования).