Найти тему
using Dev

Декоратор C#

Оглавление

Декоратор предназначен для динамического добавления объекту новой функциональности. Является гибкой альтернативой механизму наследования, в том числе и множественного.

Данный шаблон используется если необходимо:

  • динамически и прозрачно для клиента изменять функциональность объекта;
  • (или) реализовать небольшую функциональность, которая в дальнейшем может быть исключена;
  • (или) уменьшить число классов, получающихся в результате использования наследования;
  • (или) добавить функциональность классу, от который невозможно наследоваться;
  • (или) реализовать аналог множественного наследования, в языках его не поддерживающих.

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

Из этого описания можно сделать следующие выводы:

  1. Декоратор прозрачен для использования, т.к. клиент не заметит подмены компонента за счет реализации одного и того же интерфейса с ним.
  2. Добавление новой функциональности осуществляется подменой экземпляра оригинального компонента. Исключить ее так же легко – нужно использовать оригинальный объект. Причем его не обязательно пересоздавать и можно извлечь из Декоратора.
  3. Поскольку шаблон реализует интерфейс исходного компонента, то ничего не мешает вкладывать один Декоратор в другой, создавая их цепочки. Данный подход является альтернативой множественному наследованию.
  4. В шаблоне нет ограничения на добавления новых свойств и методов. Такой объект может использоваться как вместо декорируемого компонента, так и самостоятельно.
  5. Декоратор способен работать как с самим исходным компонентом, так и его наследниками.

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

Таким образом, можно выделить следующих участников шаблона Декоратор:

  1. Общий интерфейс (IComponent) – определяет интерфейс объектов и Декоратора;
  2. Конкретный компонент (Concrete component) – компонент(ы), реализующие общий интерфейс, функциональность которых необходимо модифицировать.
  3. Декоратор (Decorator) – базовый класс для декораторов, как правило включает механизм хранения компонента и реализует простую переадресацию.
  4. Конкретный декоратор (Concrete decorator) – реализация Декоратора, добавляющая определенные функции компоненту.

Особенности использования

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

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

Пример уменьшения числа классов

Предположим, необходимо разработать класс Element, реализующий элемент блок-схемы. Есть четыре стиля его отображения, которые надо комбинировать: по-умолчанию, инвертированный (inverted), зачеркнутый (striked), выделенный (highlighted). Кроме того, необходимо в дальнейшем легко добавлять новые стили и их комбинации.

При использовании наследования получим 8 классов: от Element, ElementInverted, ElementStriked, ElementHighlighted, ElementInvertedStriked и до ElementInvertedStrikedHighlighted. Добавление еще одного стиля, например рамки (ElementBorder), увеличит число классов до 16. Назвать это удобным, простым и надежным решением навряд ли можно.

Использование шаблона Декоратор ограничит число создаваемых классов до 4: основной Element и 3 Декоратора (ElementInvert, ElementStrike, ElementHighlight). В дальнейшем возможно их вкладывать один в другой для достижения нужного результата. Кроме того, такой подход более гибкий в работе. Например, можно сначала инвертировать, а потом подсветить или наоборот. Да и поддерживать такой код проще.

Отличия в применении Декоратора и механизма наследования:

Применение шаблона обеспечивает:

  • более гибкий механизм, который позволяет комбинировать классы в процессе выполнения программы;
  • возможность дважды применить один и тот же Декоратор (например, для рисования двойной рамки вокруг элемента можно дважды применить ElementBorder с разным отступом);
  • изменение функциональности не только применением самих Декораторов, но и различным порядком их вложенности (например, результат операции "инвертировать цвет + зачеркнуть" будет отличаться от результата "зачеркнуть + инвертировать цвет");
  • меньшую зависимость, за счет использования только открытых (public) методов и свойства декорируемого объекта (но это может быть и ограничением использования шаблона).

Наследование, не обеспечивая возможностей указанных, позволяет обращаться к защищенным (protected) областям базового класса. Кроме того, нет необходимости создавать механизм переадресации.

Реализация шаблона в общем виде

  • определяем общий интерфейс (IComponent) и его реализации;
  • разрабатываем базовый Декоратор (DecoratorBase), реализующий общий интерфейс (IComponent):создаем механизм подключения и хранения компонента;
    реализуем переадресацию всех методов и свойств;
  • создаем конкретные Декораторы (Decorator), используя наследование от базового;
  • в клиентском коде используем Декоратор вместо конкретного компонента;
  • при необходимости создаем цепочки Декораторов, передавая один из них в другой. Это позволяет добавить несколько новых возможностей компоненту;
  • если функции декоратора становятся не нужны, то используем вновь исходный объект.

Пример реализации

На примере формирования данных для передачи по каналу связи рассмотрим применение данного паттерна.

Создадим интерфейс IProcessor

-2

Создадим класс, реализующий интерфейс.

-3

Создадим декоратор ввиде абстрактного класса Shell.

-4

Далее создадим класс HammingCoder, наследуемый от абстрактного Shell, данный класс будет представлять из себя устройство по наложению помехоустойчивого кода Хэминга на передаваемые данные.

-5

По аналогии создадим класс Encryptor данный класс будет выступать в качестве устройства шифрования переданных данных.

-6

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

-7
-8

Наука
7 млн интересуются