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

Ответ

Decorator и Adapter — оба структурные паттерны, работающие через композицию, но их цели противоположны.

Паттерн Decorator

Назначение: Динамически добавляет новую функциональность объекту, не изменяя его исходный класс. Следует принципу открытости/закрытости.

Как работает: Обёртывает исходный объект, делегируя ему вызовы и добавляя своё поведение до или после.

Пример на C#:

// Базовый компонент
public interface IDataSource
{
    string ReadData();
    void WriteData(string data);
}

// Конкретная реализация
public class FileDataSource : IDataSource { /* Чтение/запись в файл */ }

// Базовый декоратор
public abstract class DataSourceDecorator : IDataSource
{
    protected IDataSource _wrappedDataSource;
    protected DataSourceDecorator(IDataSource source) => _wrappedDataSource = source;
    public virtual string ReadData() => _wrappedDataSource.ReadData();
    public virtual void WriteData(string data) => _wrappedDataSource.WriteData(data);
}

// Конкретный декоратор — добавляет шифрование
public class EncryptionDecorator : DataSourceDecorator
{
    public EncryptionDecorator(IDataSource source) : base(source) { }
    public override string ReadData()
    {
        string encrypted = _wrappedDataSource.ReadData();
        return Decrypt(encrypted); // Добавленное поведение
    }
    public override void WriteData(string data)
    {
        string encrypted = Encrypt(data); // Добавленное поведение
        _wrappedDataSource.WriteData(encrypted);
    }
    private string Encrypt(string data) { /* ... */ }
    private string Decrypt(string data) { /* ... */ }
}
// Использование: можно обернуть FileDataSource в EncryptionDecorator, а затем в CompressionDecorator.

Паттерн Adapter

Назначение: Преобразует интерфейс одного класса в интерфейс, ожидаемый клиентом. Используется для интеграции несовместимых компонентов.

Как работает: Обёртывает несовместимый объект (Adaptee) и реализует целевой интерфейс (Target), переводя вызовы.

Пример на C#:

// Целевой интерфейс, который ожидает клиент (новая система)
public interface IModernLogger
{
    void Log(string message, LogLevel level);
}

// Устаревший класс, который нужно адаптировать (старая библиотека)
public class LegacyFileLogger
{
    public void WriteToFile(string message) // Несовместимый метод
    {
        File.AppendAllText("log.txt", message + "n");
    }
}

// Адаптер
public class LegacyLoggerAdapter : IModernLogger
{
    private readonly LegacyFileLogger _legacyLogger;
    public LegacyLoggerAdapter(LegacyFileLogger logger) => _legacyLogger = logger;

    public void Log(string message, LogLevel level)
    {
        // Преобразуем вызов нового интерфейса в старый
        string formattedMessage = $"[{DateTime.Now}] [{level}]: {message}";
        _legacyLogger.WriteToFile(formattedMessage);
    }
}
// Использование: клиент работает с IModernLogger, не зная о LegacyFileLogger.

Ключевые отличия

Аспект Decorator Adapter
Цель Расширить функциональность объекта. Изменить интерфейс объекта для совместимости.
Взаимодействие Декоратор и обёрнутый объект реализуют один и тот же интерфейс. Адаптер реализует целевой интерфейс, но оборачивает объект с другим интерфейсом.
Время применения Часто планируется изначально для гибкого добавления функций. Применяется постфактум, когда нужно интегрировать чужой/устаревший код.
Изменение поведения Добавляет новое поведение. Преобразует (адаптирует) существующее поведение.

Ответ 18+ 🔞

А, слушай, вот тебе наглядная разница между Декоратором и Адаптером, чтобы в голове не каша была. Это как сравнивать тюнингованную тачку и переходник для розетки в чужой стране — оба что-то оборачивают, но цели у них, блядь, с разных планет.

Декоратор (Decorator) — это когда ты хочешь НАВЕСИФИГАРИТЬ

Суть: У тебя есть базовый объект, и ты хочешь его обогатить новыми фичами, но так, чтобы его родной код остался святым и неприкосновенным. Как будто ты кастомизируешь свой комп: поставил подсветку, потом водяное охлаждение, потом ещё вентиляторов нахуячил. Каждый новый слой добавляет функционал, но комп-то остаётся тем же самым — ты в него игры запускаешь.

Как работает на пальцах: Берёшь объект, заворачиваешь его в декоратор, и этот декоратор делает всё то же самое, что и оригинал, плюс что-то своё сверху. И так можно оборачивать в декораторы бесконечно, как матрёшку.

Вот смотри, пример на C#:

// Это наш базовый интерфейс. Допустим, это просто "Источник данных".
public interface IDataSource
{
    string ReadData();
    void WriteData(string data);
}

// Конкретная реализация — работает с файлом. Всё просто.
public class FileDataSource : IDataSource
{
    // ... тут чтение/запись в файл, обычный такой функционал
}

// А вот базовый декоратор. Он тоже IDataSource, но внутри у него спрятан другой IDataSource.
public abstract class DataSourceDecorator : IDataSource
{
    protected IDataSource _wrappedDataSource; // Вот он, обёрнутый объект!
    protected DataSourceDecorator(IDataSource source) => _wrappedDataSource = source;

    // Делегируем вызовы внутрь, к обёрнутому объекту.
    public virtual string ReadData() => _wrappedDataSource.ReadData();
    public virtual void WriteData(string data) => _wrappedDataSource.WriteData(data);
}

// А теперь конкретный декоратор, который добавляет шифрование. Вот где магия!
public class EncryptionDecorator : DataSourceDecorator
{
    public EncryptionDecorator(IDataSource source) : base(source) { }

    public override string ReadData()
    {
        // 1. Сначала читаем данные у обёрнутого объекта (например, FileDataSource)
        string encrypted = _wrappedDataSource.ReadData();
        // 2. А потом ДОБАВЛЯЕМ своё — расшифровываем.
        return Decrypt(encrypted);
    }

    public override void WriteData(string data)
    {
        // 1. Сначала ДОБАВЛЯЕМ своё — шифруем данные.
        string encrypted = Encrypt(data);
        // 2. Потом передаём зашифрованное обёрнутому объекту.
        _wrappedDataSource.WriteData(encrypted);
    }

    private string Encrypt(string data) { /* ... */ }
    private string Decrypt(string data) { /* ... */ }
}

// Использовать — одно удовольствие:
IDataSource source = new FileDataSource();
source = new EncryptionDecorator(source); // Обернули в шифрование
// А можно и ещё! source = new CompressionDecorator(source); // Поверх шифрования навесили сжатие
// И всё работает, как будто так и было задумано.

Итог по Декоратору: Ты не меняешь старую функциональность, ты её наращиваешь, как слоёный пирог. Все участники говорят на одном языке (один интерфейс).


Адаптер (Adapter) — это когда надо СВЯЗАТЬ ХУЙ С ПАЛЬЦЕМ

Суть: У тебя есть какой-то старый, кривой, легаси-класс с ебанутым интерфейсом, а твоя новая система требует от него совсем другого. Задача адаптера — сделать вид, что этот старый класс и есть тот самый нужный интерфейс. Это как взять вилку от советского утюга и через переходник воткнуть её в евро-розетку. Ты не меняешь утюг, ты меняешь вид его подключения.

Как работает на пальцах: Адаптер оборачивает объект с НЕПОДХОДЯЩИМ интерфейсом и представляет его миру как объект с ПРАВИЛЬНЫМ интерфейсом. Внутри адаптера происходит всякая грязная работа по переводу вызовов.

Держи пример:

// Это новый, современный интерфейс, который ждёт твоя система.
public interface IModernLogger
{
    void Log(string message, LogLevel level); // Красиво, с уровнем логирования
}

// А это старый, убогий класс из библиотеки, которую нельзя трогать.
public class LegacyFileLogger
{
    // У него метод называется по-другому и принимает просто строку. Совсем не то!
    public void WriteToFile(string message)
    {
        File.AppendAllText("log.txt", message + "n");
    }
}

// Вот он, наш спаситель-адаптер. Он реализует НОВЫЙ интерфейс...
public class LegacyLoggerAdapter : IModernLogger
{
    private readonly LegacyFileLogger _legacyLogger; // ...но внутри держит СТАРЫЙ объект.
    public LegacyLoggerAdapter(LegacyFileLogger logger) => _legacyLogger = logger;

    // И вот здесь происходит подмена. Клиент думает, что вызывает Log(...),
    // а адаптер это преобразует в вызов старого метода WriteToFile.
    public void Log(string message, LogLevel level)
    {
        // Преобразуем вызов нового интерфейса в формат, понятный старой библиотеке.
        string formattedMessage = $"[{DateTime.Now}] [{level}]: {message}";
        _legacyLogger.WriteToFile(formattedMessage); // Вот и всё, магия адаптации.
    }
}

// Использование:
LegacyFileLogger oldShittyLogger = new LegacyFileLogger();
IModernLogger logger = new LegacyLoggerAdapter(oldShittyLogger); // Обернули в адаптер
// Теперь вся система работает с logger через IModernLogger, даже не подозревая,
// что внутри булькает древний код.

Итог по Адаптеру: Ты не добавляешь новую функциональность, ты притворяешься. Ты берёшь один интерфейс и подсовываешь под него другой, как протез. Клиент счастлив, старый код не тронут, а ты — герой, который всё связал.

Так в чём, блядь, разница? Краткая шпаргалка:

Что сравниваем Decorator Adapter
Зачем? Чтобы улучшить и расширить объект новыми фичами. Чтобы подогнать ебанутый интерфейс объекта под нужный.
Отношения Декоратор и обёрнутый объект — братья, у них один интерфейс. Адаптер и обёрнутый объект — чужие, у них разные интерфейсы.
Когда юзать? Когда заранее продумываешь архитектуру, чтобы можно было гибко добавлять возможности. Когда приспичило впихнуть чужой или старый код в свою систему, а переписывать его — овердохуя работы.
Что делает? Добавляет поведение поверх существующего. Переводит одно поведение в другое, как словарь.

Проще говоря: Декоратор — это когда ты на базовую модель телефона клеишь защитное стекло, потом чехол, потом PopSocket. Адаптер — это когда у тебя зарядка от iPhone, а розетка в отеле — европейская, и ты суёшь между ними переходник. И да, оба паттерна используют композицию, но цели у них, ёпта, как небо и земля.