- •Лабораторная работа №4 Шаблоны проектирования
- •Теоретические сведения
- •Отношения между классами. На диаграммах классов языка uml
- •Суть паттерна
- •Решение
- •Адаптер объектов
- •Адаптер классов
- •Адаптер классов не нуждается во вложенном объекте, так как он может одновременно наследовать и часть существующего класса, и часть сервиса.
- •Фасад (Facade)
- •Концептуальный пример
- •Program.Cs:
- •Output.Txt: Результат выполнения
- •Изолирует клиентов от компонентов подсистемы Уменьшая тем самым число объектов, с которыми клиентам приходится иметь дело, упрощая работу с подсистемой.
- •Позволяет ослабить связанность между подсистемой и ее клиентами.
- •Фасад не исключает возможности приложениям напрямую обращаться к классам подсистемы, если это необходимо.
- •Заместитель (Proxy)
- •3. Локальный запуск сервиса (удалённый прокси). Когда настоящий сервисный объект находится на удалённом сервере.
- •Концептуальный пример
- •Декоратор (Decorator)
- •// Объект
- •} //Ptr2 выходит из области видимости, но объект не //освобождается, потому что есть ptr, который по-прежнему //ссылается на него } //ptr выходит из области видимости, и объект уничтожается
- •Пример на языке c#
- •Порождающие шаблоны Абстрактная фабрика (Abstract Factory)
- •1. Шаблон реализуется созданием абстрактного класса Factory, который представляет собой интерфейс для создания компонентов системы.
- •3. Таким образом, еще раз - предоставляет интерфейс для создания семейств, связанных между собой, или зависимых объектов.
- •Клиент пользуется только интерфейсами, заданными в классах «Абстрактная фабрика» и «Абстрактный продукт».
- •Фабричный метод (Factory Method)
- •Void info() {
- •Void info() {
- •Void info() {
- •Int main()
- •Одиночка (Singleton) Суть паттерна
- •If(!p_instance)
- •Поведенческие шаблоны Стратегия (Strategy)
- •Стратегии построения пути.
- •Структура
- •Концептуальный пример
- •Program.Cs: Пример структуры паттерна
- •Output.Txt: Результат выполнения
- •Void useStrategy(void)
- •Void setStrategy(Strategy* o)
- •Int main(int /*argc*/, char* /*argv*/[])
- •Наблюдатель (Observer) Суть паттерна
- •Решение
- •Структура
- •Шаги реализации
- •Концептуальный пример
- •Program.Cs: Пример структуры паттерна
- •// Random.Next(…) - Метод, возвращает случайное целое число //в указанном диапазоне.
- •Output.Txt: Результат выполнения
- •Использование паттерна Observer
- •Команда (Command)
- •Структура
- •Output.Txt: Результат выполнения
- •Задания для лабораторной работы
Декоратор (Decorator)
Декоратор — структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту.
Шаблон Декоратор предоставляет гибкую альтернативу практике создания подклассов с целью расширения функциональности.
Суть паттерна
Декоратор — это структурный паттерн проектирования, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обёртки».
Решение
Наследование — это первое, что приходит в голову многим программистам, когда нужно расширить какое-то существующее поведение. Но механизм наследования имеет несколько досадных проблем. Он статичен. Вы не можете изменить поведение существующего объекта. Для этого вам надо создать новый объект, выбрав другой подкласс. Из-за этого вам приходится создавать множество подклассов-комбинаций для получения совмещённого поведения.
Одним из способов обойти эти проблемы является замена наследования агрегацией либо композицией.
Это когда один объект содержит ссылку на другой и делегирует ему работу, вместо того чтобы самому наследовать его поведение. Как раз на этом принципе построен паттерн Декоратор.
Наследование против Агрегации.
Декоратор имеет альтернативное название — обёртка. Оно более точно описывает суть паттерна: вы помещаете целевой объект в другой объект-обёртку, который запускает базовое поведение объекта, а затем добавляет к результату что-то своё.
Оба объекта имеют общий интерфейс, поэтому для пользователя нет никакой разницы, с каким объектом работать — чистым или обёрнутым. Вы можете использовать несколько разных обёрток одновременно — результат будет иметь объединённое поведение всех обёрток сразу.
Структура
- Компонент задаёт общий интерфейс обёрток и оборачиваемых объектов.
- Конкретный компонент определяет класс оборачиваемых объектов. Он содержит какое-то базовое поведение, которое потом изменяют декораторы.
- Базовый декоратор хранит ссылку на вложенный объект-компонент. Им может быть как конкретный компонент, так и один из конкретных декораторов. Базовый декоратор делегирует все свои операции вложенному объекту. Дополнительное поведение будет жить в конкретных декораторах.
- Конкретные декораторы — это различные вариации декораторов, которые содержат добавочное поведение. Оно выполняется до или после вызова аналогичного поведения обёрнутого объекта.
- Клиент может оборачивать простые компоненты и декораторы в другие декораторы, работая со всеми объектами через общий интерфейс компонентов.
Применимость
- Когда вам нужно добавлять обязанности объектам на лету, незаметно для кода, который их использует.
- Объекты помещают в обёртки, имеющие дополнительные поведения. Обёртки и сами объекты имеют одинаковый интерфейс, поэтому клиентам без разницы, с чем работать — с обычным объектом данных или с обёрнутым.
- Когда нельзя расширить обязанности объекта с помощью наследования.
- Во многих языках программирования есть ключевое слово final, которое может заблокировать наследование класса. Расширить такие классы можно только с помощью Декоратора.
Шаги реализации
Убедитесь, что в вашей задаче есть один основной компонент и несколько опциональных дополнений или надстроек над ним.
Создайте интерфейс компонента, который описывал бы общие методы как для основного компонента, так и для его дополнений.
Создайте класс конкретного компонента и поместите в него основную бизнес-логику.
Создайте базовый класс декораторов. Он должен иметь поле для хранения ссылки на вложенный объект-компонент. Все методы базового декоратора должны делегировать действие вложенному объекту.
И конкретный компонент, и базовый декоратор должны следовать одному и тому же интерфейсу компонента.
Теперь создайте классы конкретных декораторов, наследуя их от базового декоратора. Конкретный декоратор должен выполнять свою добавочную функцию, а затем (или перед этим) вызывать эту же операцию обёрнутого объекта.
Клиент берёт на себя ответственность за конфигурацию и порядок обёртывания объектов.
Преимущества и недостатки
- Большая гибкость, чем у наследования.
- Позволяет добавлять обязанности на лету.
- Можно добавлять несколько новых обязанностей сразу.
- Позволяет иметь несколько мелких объектов вместо одного объекта на все случаи жизни.
- Трудно конфигурировать многократно обёрнутые объекты.
- Обилие крошечных классов.
Отношения с другими паттернами
Адаптер меняет интерфейс существующего объекта. Декоратор улучшает другой объект без изменения его интерфейса. Причём Декоратор поддерживает рекурсивную вложенность, чего не скажешь об Адаптере.
Адаптер предоставляет классу альтернативный интерфейс.
Декоратор предоставляет расширенный интерфейс.
Заместитель предоставляет тот же интерфейс.
Цепочка обязанностей и Декоратор имеют очень похожие структуры. Оба паттерна базируются на принципе рекурсивного выполнения операции через серию связанных объектов. Но есть и несколько важных отличий.
Обработчики в Цепочке обязанностей могут выполнять произвольные действия, независимые друг от друга, а также в любой момент прерывать дальнейшую передачу по цепочке. С другой стороны Декораторы расширяют какое-то определённое действие, не ломая интерфейс базовой операции и не прерывая выполнение остальных декораторов.
Компоновщик и Декоратор имеют похожие структуры классов из-за того, что оба построены на рекурсивной вложенности. Она позволяет связать в одну структуру бесконечное количество объектов.
Декоратор оборачивает только один объект, а узел Компоновщика может иметь много детей. Декоратор добавляет вложенному объекту новую функциональность, а Компоновщик не добавляет ничего нового, но «суммирует» результаты всех своих детей.
Но они могут и сотрудничать: Компоновщик может использовать Декоратор, чтобы переопределить функции отдельных частей дерева компонентов.
Архитектура, построенная на Компоновщиках и Декораторах, часто может быть улучшена за счёт внедрения Прототипа. Он позволяет клонировать сложные структуры объектов, а не собирать их заново.
Стратегия меняет поведение объекта «изнутри», а Декоратор изменяет его «снаружи».
Декоратор и Заместитель имеют схожие структуры, но разные назначения. Они похожи тем, что оба построены на композиции и делегируют работу другим объектам. Паттерны отличаются тем, что Заместитель сам управляет жизнью сервисного объекта, а обёртывание Декораторов контролируется клиентом.
Декоратор на 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> (); //создаётся
