Декоратор – структурный шаблон проектирования, предназначенный для динамического подключения дополнительного поведения к объекту. Декоратор представляет гибкую альтернативу создания подклассов с целью расширения функционала.
Данный паттерн еще называют обёрткой, но лично мне нравится аналогия с матрешкой. Ведь он позволяет добавить функциональности объекту, которая будет выполняться до, после или вместо основного функционала. Тем самым создавая некую вложенность функционала одного объекта в другой.
Описанную задачу можно решать двумя способами. С помощью композиции и с помощью наследования. Но наследование обеспечивает более жесткую связь, по сравнению с композицией. Также композиция позволяет расширять поведение во время исполнения программы, в то время как с помощью наследования можно расширять функционал только лишь во время компиляции.
Переходя к практической задаче, давайте реализуем классы для различного форматирования текста. Не будем добавлять что-то сложное, так как простых понятных классов будут достаточно для понимания самого паттерна. Добавим классы, которые - приводит текст к верхнему регистру, убирает пробелы из текста, добавляет текст в HTML шаблон, преобразует текст в base64.
Используя шаблон «Декоратор», мы сможем вызывать данные классы в любом порядке, влаживая функционал классов один в другой. Формируя тот результат, который нам необходим.
Сперва создадим базовый класс для работы с текстом, назовём его EditOfTextBase. Именно этот класс будет являться декоратором. Определим его абстрактным. В конструкторе будем передавать объект этого же типа, при этом сам объект для передачи сделаем не обязательным. Добавим виртуальный метод GetFormattedText который принимает текст и возвращает текст. Но при этом, переданный текст мы может обработать методом GetFormattedText объекта, переданного в конструкторе. В том случае, если он был передан.
public abstract class EditOfTextBase
{
private readonly EditOfTextBase _editOfTextBase;
protected EditOfTextBase(EditOfTextBase editOfTextBase = null)
{
_editOfTextBase = editOfTextBase;
}
public virtual string GetFormattedText(string text)
{
if (_editOfTextBase != null)
{
text = _editOfTextBase.GetFormattedText(text);
}
return text;
}
}
Теперь перейдём к созданию конкретных декораторов. Добавим класс ToUpperText, наследуем его от класса EditOfTextBase. В конструкторе передаём переданный объект в базовый класс. Перегружаем метод GetFormattedText. Весь полученный в нем текст приведём к верхнему регистру. Первый конкретный декоратор готов.
class ToUpperText : EditOfTextBase
{
public ToUpperText(EditOfTextBase editOfTextBase = null) : base(editOfTextBase)
{
}
public override string GetFormattedText(string text)
{
return base.GetFormattedText(text)?.ToUpper();
}
}
Аналогично добавим класс для замены пробелов, ReplaceSpacesWithLog. В методе GetFormattedText будем заменять пробелы на символы + и выводить в консоль предупреждение, что символ будут заменён.
class ReplaceSpacesWithLog : EditOfTextBase
{
public ReplaceSpacesWithLog(EditOfTextBase editOfTextBase = null) : base(editOfTextBase)
{
}
public override string GetFormattedText(string text)
{
Console.WriteLine($"All ' ' will replace to '+'");
return base.GetFormattedText(text)?.Replace(' ', '+');
}
}
Добавим класс ToBase64,в методе GetFormattedText будет преобразовывать переданный текст в base64
class ToBase64 : EditOfTextBase
{
public ToBase64(EditOfTextBase editOfTextBase = null) : base(editOfTextBase)
{
}
public override string GetFormattedText(string text)
{
var textBytes = Encoding.UTF8.GetBytes(text);
return Convert.ToBase64String(textBytes);
}
}
И так же добавим класс ToHtmlTemplate, который будет вставлять переданный текст в HTML шаблон. При этом новые строки разделяя HTML тегом перехода на новую строку.
class ToHtmlTemplate : EditOfTextBase
{
private readonly string _htmlPage = "<!DOCTYPE html>\n<html>\n<body>\n<div>{0}</div>\n</body>\n</html>";
public ToHtmlTemplate(EditOfTextBase editOfTextBase = null) : base(editOfTextBase)
{
}
public override string GetFormattedText(string text)
{
var lines = base.GetFormattedText(text).Split(new[] {'\n'}, StringSplitOptions.RemoveEmptyEntries);
return string.Format(_htmlPage, string.Join("<br/>\n", lines));
}
}
Несмотря на простоту реализации и использования данного паттерна. У его есть несколько недостатков. Во-первых, он чувствителен к порядку. В зависимости от того в каком порядке мы будем вызывать декораторы, будет меняться результат. Во-вторых, при использовании декораторов возрастает сложность отладки, особенно для тех, кто не знаком с данным паттерном.
Декораторы идеально подходят, когда вам нужно динамически добавлять новое поведение, которое не является частью основной функциональности объекта.
Приятного программирования.