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

В чем разница между декоратором и адаптером?

1.0 Junior🔥 81 комментариев
#Основы Java

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

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

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

# Разница между 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}); // Сначала зашифрует, потом упакует

Наглядное сравнение

АспектAdapterDecorator
ЦельСогласовать интерфейсыДобавить функциональность
НазначениеЧтобы старый код работал с новым интерфейсомРасширить возможности объекта
Структура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");

Ключевые различия

  1. Adapter решает проблему несовместимости, Decorator расширяет функциональность
  2. Adapter обычно один, Decorator часто применяется многократно
  3. Adapter меняет контракт, Decorator его сохраняет
  4. Adapter для интеграции, Decorator для расширения

Выбор между ними проост: если вам нужно заставить работать несовместимое, используйте Adapter. Если нужно добавить функциональность — Decorator.