Также известен как Обёртка, Decorator

Декоратор

Суть паттерна

Динамически добавляет объектам новую функциональность, оборачивая их в полезные «обёртки».

Проблема

Вам нужно динамически добавлять и снимать с объекта новые обязанности таким образом, чтобы он оставался совместим с остальным кодом программы.

Наследование — первое что приходит в голову, если вам нужно добавить новое поведение объекту. Но механизм наследования статичен — в программу нельзя на лету добавлять подклассы.

Решение

Декоратор имеет ещё одно название — Обёртка. Оно удачнее описывает суть паттерна.

Итак, с Декоратором вы помещаете целевой объект в другой объект-обёртку, которая придаёт начальному объекту дополнительные функции.

Оба объекта имеют общий интерфейс, поэтому пользователю всё равно с чем работать — с чистым объектом или обёрнутым.

Вы можете использовать несколько разных обёрток одновременно — результат будет иметь функции всех обёрток сразу.

Аналогия из жизни

Одежда

Любая одежда — это пример Декоратора. Применяя декоратор, вы не меняете первоначальный класс и не создаёте дочерних классов.

Надевая свитер, вы не перестаёте быть собой, но получаете новое свойство — защиту от холода. Вы можете пойти дальше и надеть ещё один «декоратор» — плащ — чтобы защититься и от дождя.

Структура

Схема структуры классов паттерна Декоратор
  1. Компонент определяет общий интерфейс обёрток и оборачиваемых объектов.

  2. Конкретный Компонент определяет класс оборачиваемых объектов. Он хранит поведение объекта по умолчанию.

  3. Базовый Декоратор хранит ссылку на объект-компонент. Ним может быть как Конкретный компонент, так и один из Конкретных декораторов.

    Декоратор делегирует все свои операции вложенному объекту. Дополнительное поведение содержится в Конкретных декораторах.

  4. Конкретные Декораторы — это различные вариации декораторов, которые содержат добавочное поведение. Оно выполняется до или после вызова аналогичного поведения обёрнутого объекта.

Псевдокод

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

Таким образом, паттерн Декоратор позволяет добавлять объектам новые свойства незаметно для клиентского кода. Объекты могут оборачиваться множеством обёрток одновременно, получая свойства всех этих обёрток сразу.

// Общий интерфейс компонентов.
interface DataSource is
    method writeData(data)
    method readData():data

// Один из конкретных компонент, реализует базовую функциональность.
class FileDataSource implements DataSource is
    constructor FileDataSource(filename) { ... }

    method writeData(data) is
        Write data to file.

    method readData():data is
        Read data from file.

// Родитель всех Декораторов содержит код обёртывания.
class DataSourceDecorator implements DataSource is
    field wrappee: DataSource

    constructor DataEncyptionDecorator(source: DataSource) is
        wrappee = source

    method writeData(data) is
        wrappee.writeData(data)

    method readData():data is
        return wrappee.readData()

// Конкретные Декораторы расширяют базовое поведение компонента.
class EncyptionDecorator extends DataSourceDecorator is
    method writeData(data) is
        Encrypt passed data.
        Pass the compressed data to wrappee's writeData() method.

    method readData():data is
        Get the data from wrappee's readData() method.
        Decrypt and return that data.

// Декорировать можно не только базовые компоненты, но и уже обёрнутые объекты.
class CompressionDecorator extends DataSourceDecorator is
    method writeData(data) is
        Compress passed data
        Pass the compressed data to wrappee's writeData() method.

    method readData():data is
        Get the data from wrappee's readData() method.
        Uncompress and return that data.


// Вариант 1. Простой пример сборки и использования декораторов.
class Application is
    method dumbUsageExample() is
        source = new FileDataSource('somefile.dat')
        source.writeData(salaryRecords)
        // в файл записаны чистые данные

        source = new CompressionDecorator(source)
        source.writeData(salaryRecords)
        // файл сжат

        source = new EncyptionDecorator(source)
        source.writeData(salaryRecords)
        // файл сжат и зашифрован



// Вариант 2. Клиентский код, использующий внешний источник данных. Класс
// SalaryManager ничего не знает о том как именно будут считаны и записаны
// данные. Он получает уже готовый источник данных.
class SalaryManager is
    field source: DataSource

    constructor SalaryManager(source: DataSource) { ... }

    method load() is
        return source.readData()

    method save() is
        source.writeData(salaryRecords)
    // ...Остальные полезные методы...


// Приложение может по-разному собирать декорируемые объекты, в зависимости от
// условий использования.
class ApplicationConfigurator is
    method configurationExample() is
        source = new FileDataSource("salary.dat");
        if (enabledEncryption)
            source = new EncyptionDecorator(source)
        if (enabledCompression)
            source = new CompressionDecorator(source)

        logger = new SalaryLogger(source)
        salary = logger.load();
    // ...Остальной код приложения

Применимость

Вам нужно добавлять обязанности объектам на лету, незаметно для кода, который их использует.

Объекты помещают в обёртки, имеющие дополнительные поведения. Обёртки и сами объекты имеют одинаковый интерфейс, а значит для клиентов нет разницы с чем работать — обычным или обёрнутым объектом.

Если нельзя расширить обязанности объекта с помощью наследования.

Во многих языках программирования есть ключевое слово final, которое может заблокировать наследование класса. Расширить такие классы можно только с помощью Декоратора.

Шаги реализации

  1. Убедитесь, что у в вашей задаче есть один основной компонент и несколько опциональных дополнений или надстроек над ним.

  2. Создайте интерфейс Компонента, который содержал бы все общие методы для основного компонента и его дополнений.

  3. Создайте класс Конкретного компонента и поместите в него основную бизнес-логику.

  4. Создайте базовый класс декораторов. Он должен содержать ссылку на объект типа Компонент.

  5. Оба класса должны реализовывать интерфейс Компонента.

  6. Все методы Базового декоратора должны делегировать действие оборачиваемому объекту.

  7. Создайте классы конкретных декораторов, наследуя их от Базового декоратора.

  8. Конкретный декоратор должен выполнять свою добавочную функциональность, а затем (или перед этим) вызывать эту же операцию обёрнутого объекта.

  9. Клиент берёт на себя ответственность за конфигурацию и порядок обёртывания объектов.

Преимущества и недостатки

  • Большая гибкость, чем у наследования.
  • Позволяет добавлять обязанности на лету.
  • Можно добавлять несколько новых обязанностей сразу.
  • Позволяет избежать перегруженных классов на верхних уровнях иерархии.
  • Трудно конфигурировать многократно обёрнутые объекты.
  • Обилие крошечных классов.

Отношения с другими паттернами

  • Адаптер предоставляет классу альтернативный интерфейс. Заместитель предоставляет тот же интерфейс. Декоратор предоставляет расширенный интерфейс.

  • Адаптер меняет интерфейс существующего объекта. Декоратор улучшает другой объект без изменения его интерфейса. Причём Декоратор поддерживает рекурсивную вложенность, чего не скажешь об Адаптере.

  • Компоновщик и Декоратор имеют похожие структуры классов из-за того, что оба построены на рекурсивной вложенности. Она позволяет связать в одну структуру бесконечное количество объектов. Декоратор оборачивает только один объект, а узел Компоновщика может иметь много детей. Декоратор добавляет вложенному объекту новую функциональность, а Компоновщик не добавляет ничего нового, но «суммирует» результаты всех своих детей. Компоновщик может использовать Декоратор, чтобы переопределить функции отдельных частей дерева компонентов.

  • Архитектура, построенная на Компоновщиках и Декораторах часто может быть улучшена за счёт внедрения Прототипа. Он позволил бы клонировать сложные структуры, а не собирать заново.

  • Стратегия меняет поведение объекта «изнутри», а Декоратор изменяет его «снаружи».

  • Декоратор и Заместитель имеют похожие структуры, но разные назначения. Они похожи тем, что оба построены на композиции и делегировании работы другому объекту. Паттерны отличаются тем, что Заместитель сам управляет жизнью сервисного объекта, а обёртывание Декораторов контролируется клиентом.

Реализация в различных языках программирования

Java