Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
лабы / Лабораторная_работа_по_паттернам23.docx
Скачиваний:
0
Добавлен:
11.02.2026
Размер:
1.18 Mб
Скачать

Декоратор (Decorator)

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

Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.

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

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

Решение

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

Одним из способов обойти эти проблемы является замена наследования агрегацией либо композицией.

Это когда один объект содержит ссылку на другой и делегирует ему работу, вместо того чтобы самому наследовать его поведение. Как раз на этом принципе построен паттерн Декоратор.

Наследование против Агрегации.

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

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

  Структура

- Компонент задаёт общий интерфейс обёрток и оборачиваемых объектов.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  •   - Большая гибкость, чем у наследования.

  •   - Позволяет добавлять обязанности на лету.

  •   - Можно добавлять несколько новых обязанностей сразу.

  •   - Позволяет иметь несколько мелких объектов вместо одного объекта на все случаи жизни.

  •   - Трудно конфигурировать многократно обёрнутые объекты.

  •   - Обилие крошечных классов.

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

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

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

    • Декоратор предоставляет расширенный интерфейс. 

    • Заместитель предоставляет тот же интерфейс.

  • Цепочка обязанностей и Декоратор имеют очень похожие структуры. Оба паттерна базируются на принципе рекурсивного выполнения операции через серию связанных объектов. Но есть и несколько важных отличий.

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

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

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

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

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

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

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

Декоратор на C#

Декоратор — это структурный паттерн, который позволяет добавлять объектам новые поведения на лету, помещая их в объекты-обёртки.

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

Концептуальный пример

Этот пример показывает структуру паттерна Декоратор, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.

 Program.cs: Пример структуры паттерна

using System;

namespace DesignPatterns.Composite.Conceptual

{

// Базовый интерфейс Компонента определяет поведение, которое //изменяется декораторами.

public abstract class Component

{

public abstract string Operation();

}

// Конкретные Компоненты предоставляют реализации поведения по

// умолчанию. Может быть несколько вариаций этих классов.

class ConcreteComponent : Component

{

public override string Operation ()

{

return "ConcreteComponent";

}

}

// Базовый класс Декоратора следует тому же интерфейсу, что и //другие компоненты. Основная цель этого класса - определить //интерфейс

// обёртки для всех конкретных декораторов. Реализация кода обёртки //по умолчанию может включать в себя поле для хранения завёрнутого

// компонента и средства его инициализации.

abstract class Decorator: Component

{ protected Component _component;

public Decorator(Component component)

{

this._component = component;

}

public void SetComponent (Component component)

{

this._component = component;

}

// Декоратор делегирует всю работу обёрнутому компоненту.

public override string Operation ()

{

if (this._component!= null)

{

return this._component.Operation();

}

else

{

return string.Empty;

}

}

}

// Конкретные Декораторы вызывают обёрнутый объект и изменяют

//его результат некоторым образом.

class ConcreteDecoratorA : Decorator

{

public ConcreteDecoratorA(Component comp) : base(comp)

{

}

// Декораторы могут вызывать родительскую реализацию операции,

// вместо того, чтобы вызвать обёрнутый объект напрямую. Такой

// подход упрощает расширение классов декораторов.

public override string Operation ()

{

return $"ConcreteDecoratorA({base.Operation()})";

}

}

// Декораторы могут выполнять своё поведение до или после вызова

// обёрнутого объекта.

class ConcreteDecoratorB : Decorator

{

public ConcreteDecoratorB(Component comp) : base(comp)

{

}

public override string Operation()

{

return $"ConcreteDecoratorB({base.Operation()})";

}

}

public class Client

{

// Клиентский код работает со всеми объектами, используя //интерфейс

// Компонента. Таким образом, он остаётся независимым от

// конкретных классов компонентов, с которыми работает.

public void ClientCode(Component component)

{

Console.WriteLine("RESULT: " + component.Operation());

}

}

class Program

{

static void Main(string[] args)

{

Client client = new Client();

var simple = new ConcreteComponent();

Console.WriteLine("Client: I get a simple component:");

client.ClientCode(simple);

Console.WriteLine();

// ...так и декорированные.

//

// Обратите внимание, что декораторы могут обёртывать не

// только простые компоненты, но и другие декораторы.

ConcreteDecoratorA decorator1 = new ConcreteDecoratorA(simple);

ConcreteDecoratorB decorator2 = new ConcreteDecoratorB(decorator1);

Console.WriteLine("Client: Now I've got a decorated component:");

client.ClientCode(decorator2);

}

}

}

 Output.txt: Результат выполнения

Client: I get a simple component:

RESULT: ConcreteComponent

Client: Now I've got a decorated component:

RESULT:

ConcreteDecoratorB (ConcreteDecoratorA (ConcreteComponent))

Output.txt:

Клиент: Я получаю простой компонент:

RESULT: Конкретный Компонент

Клиент: Теперь у меня есть декорированный компонент:

RESULT: ConcreteDecoratorB (КонкретныйДекораторА (Конкретный Компонент))

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

Решение: шаблон Декоратор предусматривает расширение функциональности объекта без определения подклассов.

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

Класс ConcreteComponent класс, в который с помощью шаблона Декоратор добавляется новая функциональность.

В некоторых случаях базовая функциональность предоставляется классами, производными от класса ConcreteComponent .

В подобных случаях класс ConcreteComponent является уже не конкретным, а абстрактным.

Абстрактный класс Component определяет интерфейс для использования всех этих классов.

Пример реализации шаблона:

C++

Пример на языке С++

#include <iostream>

#include <memory>

class IComponent {

public:

virtual void operation() = 0;

virtual ~IComponent(){}

};

class Component : public IComponent {

public:

virtual void operation() {

std::cout<<"World!"<<std::endl;

}

};

class DecoratorOne : public IComponent {

std::shared_ptr<IComponent> m_component;

public:

DecoratorOne(IComponent* component): m_component(component) {}

virtual void operation () {

std::cout << ", ";

m_component->operation ();

}

};

class DecoratorTwo : public Component {

//std::shared_ptr<IComponent> m_component;

IComponent * m_component;

public:

DecoratorTwo (IComponent* component):

m_component (component) {}

virtual void operation () {

std::cout << "Hello";

m_component->operation ();

}

};

int main() {

DecoratorTwo obj (new DecoratorOne (new Component()));

obj.operation(); // prints "Hello, World!\n"

return 0;

}

/* template <class T>

class shared_ptr;

std::shared_ptr – умный указатель, с разделяемым владением объектом через его указатель.

Несколько указателей shared_ptr могут владеть одним и тем же объектом; объект будет уничтожен, когда последний shared_ptr, указывающий на него, будет уничтожен

Объект уничтожается с использованием delete-expression или с использованием пользовательской функции удаления объекта, переданной в конструктор shared_ptr.

std::shared_ptr - Этот умный указатель разрешает объекту иметь несколько владельцев, а когда все владельцы уничтожаются, уничтожается и объект.

Такое поведение достигается за счёт наличия специального счётчика ссылок внутри std::shared_ptr.

Каждый раз, когда такой указатель копируется, счётчик инкрементируется, а когда один из указателей уничтожается – декрементируется.

В момент, когда счётчик достигает нуля, объект уничтожается. Посмотрим на код:

{

std::shared_ptr<X> ptr = std::make_shared<X> (); //создаётся