В чем разница между декоратором и адаптером?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Разница между Decorator и Adapter
Это два структурных паттерна проектирования, которые изменяют способ работы с объектами, но делают это совершенно разными способами. Часто их путают, но их назначение кардинально отличается.
Adapter — Согласование интерфейсов
Adapter преобразует интерфейс одного класса в интерфейс другого, который ожидают клиенты. Используется когда нужно заставить работать несовместимые интерфейсы.
Цель: Адаптировать старый/чужой класс под ожидаемый интерфейс.
// Старая система — несовместимый интерфейс
public class LegacyPaymentSystem {
public void processTransaction(String cardNumber, double amount) {
System.out.println("Processing " + amount);
}
}
// Новый интерфейс, который ожидает приложение
public interface PaymentProcessor {
void pay(PaymentRequest request);
}
public class PaymentRequest {
public String cardToken;
public double amount;
}
// Adapter — адаптирует старую систему под новый интерфейс
public class LegacyPaymentAdapter implements PaymentProcessor {
private final LegacyPaymentSystem legacySystem;
public LegacyPaymentAdapter(LegacyPaymentSystem legacySystem) {
this.legacySystem = legacySystem;
}
@Override
public void pay(PaymentRequest request) {
// Преобразуем новый формат в старый
String cardNumber = decryptToken(request.cardToken);
legacySystem.processTransaction(cardNumber, request.amount);
}
private String decryptToken(String token) {
// Логика расшифровки
return token;
}
}
// Использование
PaymentProcessor processor = new LegacyPaymentAdapter(new LegacyPaymentSystem());
PaymentRequest request = new PaymentRequest();
request.cardToken = "encrypted_token";
request.amount = 99.99;
processor.pay(request); // Работает как ожидается
Decorator — Добавление функциональности
Decorator динамически добавляет новую функциональность к объекту без изменения его структуры. Используется когда нужно расширить функционал существующего класса.
Цель: Добавить новые возможности к существующему объекту во время выполнения.
// Исходный интерфейс
public interface DataSource {
byte[] readData();
void writeData(byte[] data);
}
// Базовая реализация
public class FileDataSource implements DataSource {
private String filename;
public FileDataSource(String filename) {
this.filename = filename;
}
@Override
public byte[] readData() {
// Чтение из файла
return new byte[0];
}
@Override
public void writeData(byte[] data) {
// Запись в файл
}
}
// Decorator — добавляет шифрование
public class EncryptionDecorator implements DataSource {
private final DataSource wrappedSource;
public EncryptionDecorator(DataSource wrappedSource) {
this.wrappedSource = wrappedSource;
}
@Override
public byte[] readData() {
byte[] data = wrappedSource.readData();
return decrypt(data);
}
@Override
public void writeData(byte[] data) {
byte[] encrypted = encrypt(data);
wrappedSource.writeData(encrypted);
}
private byte[] encrypt(byte[] data) {
// Логика шифрования
return data;
}
private byte[] decrypt(byte[] data) {
// Логика расшифровки
return data;
}
}
// Decorator — добавляет сжатие
public class CompressionDecorator implements DataSource {
private final DataSource wrappedSource;
public CompressionDecorator(DataSource wrappedSource) {
this.wrappedSource = wrappedSource;
}
@Override
public byte[] readData() {
byte[] data = wrappedSource.readData();
return decompress(data);
}
@Override
public void writeData(byte[] data) {
byte[] compressed = compress(data);
wrappedSource.writeData(compressed);
}
private byte[] compress(byte[] data) {
// Сжатие данных
return data;
}
private byte[] decompress(byte[] data) {
// Распаковка данных
return data;
}
}
// Использование — комбинируем декораторы
DataSource source = new FileDataSource("data.txt");
source = new EncryptionDecorator(source); // Добавляем шифрование
source = new CompressionDecorator(source); // Добавляем сжатие
byte[] result = source.readData(); // Сначала распакует, потом расшифрует
source.writeData(new byte[]{1, 2, 3}); // Сначала зашифрует, потом упакует
Наглядное сравнение
| Аспект | Adapter | Decorator |
|---|---|---|
| Цель | Согласовать интерфейсы | Добавить функциональность |
| Назначение | Чтобы старый код работал с новым интерфейсом | Расширить возможности объекта |
| Структура | 1:1 преобразование | Может быть множественная обёртка |
| Изменение контракта | Меняет интерфейс | Сохраняет интерфейс |
| Использование | Один раз | Может применяться многократно |
| Пример из реальной жизни | Переходник для вилки (220V → 110V) | Украшение на подарке (добавляет красоту) |
Другой пример: логирование
// Интерфейс бизнес-операции
public interface PaymentService {
void processPayment(String orderId, double amount);
}
// Реальная реализация
public class StripePaymentService implements PaymentService {
@Override
public void processPayment(String orderId, double amount) {
// Реальная интеграция со Stripe
}
}
// Decorator для логирования
public class LoggingPaymentDecorator implements PaymentService {
private final PaymentService wrappedService;
private final Logger logger;
public LoggingPaymentDecorator(PaymentService wrappedService, Logger logger) {
this.wrappedService = wrappedService;
this.logger = logger;
}
@Override
public void processPayment(String orderId, double amount) {
logger.info("Starting payment for order: " + orderId + ", amount: " + amount);
try {
wrappedService.processPayment(orderId, amount);
logger.info("Payment processed successfully");
} catch (Exception e) {
logger.error("Payment failed: " + e.getMessage());
throw e;
}
}
}
// Использование
PaymentService paymentService = new StripePaymentService();
PaymentService withLogging = new LoggingPaymentDecorator(paymentService, logger);
withLogging.processPayment("ORDER-123", 99.99);
Когда использовать
Adapter:
- Интеграция старого кода с новым
- Работа с внешними библиотеками с несовместимым интерфейсом
- Обёртка для API из других источников
- Преобразование формата данных
Decorator:
- Добавление функциональности без наследования
- Логирование, кэширование, валидация
- Шифрование, сжатие, форматирование
- Динамическое применение нескольких операций
- Когда нужна гибкость в комбинировании функций
Java примеры из стандартной библиотеки
Decorator в java.io
// Базовый класс
InputStream fileStream = new FileInputStream("data.zip");
// Применяем Decorators
InputStream buffered = new BufferedInputStream(fileStream);
InputStream gzipped = new GZIPInputStream(buffered);
InputStreamReader reader = new InputStreamReader(gzipped);
// Каждый добавляет свою функциональность: буферизация, разжатие, декодирование
Adapter в java.util
// Arrays.asList возвращает адаптер, который преобразует массив в List
List<String> list = Arrays.asList("a", "b", "c");
Ключевые различия
- Adapter решает проблему несовместимости, Decorator расширяет функциональность
- Adapter обычно один, Decorator часто применяется многократно
- Adapter меняет контракт, Decorator его сохраняет
- Adapter для интеграции, Decorator для расширения
Выбор между ними проост: если вам нужно заставить работать несовместимое, используйте Adapter. Если нужно добавить функциональность — Decorator.