Паттерны проектирования в .NET за 5 минут — Декоратор (Decorator)

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

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

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

Переходя к практической задаче, давайте реализуем классы для различного форматирования текста. Не будем добавлять что-то сложное, так как простых понятных классов будут достаточно для понимания самого паттерна. Добавим классы, которые - приводит текст к верхнему регистру, убирает пробелы из текста, добавляет текст в 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));
        }
    }

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

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

Приятного программирования.

Добавить комментарий