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

«В чем разница между паттернами Decorator и Adapter?» — вопрос из категории Паттерны, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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