Паттерны проектирования в .NET за 5 минут — Шаблонный метод(Template Method)

Привет. Это очередное видео из курса "Паттерны за 5 минут". И поговорим мы о паттерне "Шаблонный метод".

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

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

Также для переопределения метода, или отдельного шага метода можно использовать делегаты и это действительно удобный подход, но при этом сам шаблон становится не отличим от паттерна «Стратегия». Здесь нужно помнить, что ваша цель не реализовать какой-то шаблон проектирования в его классическом стиле, а сделать код приложения как можно более удобным.

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

Добавим новый класс StringTemplate, это будет абстрактный класс. Добавим абстрактные методы GetFirstLine – для получения первой строки шаблона. Метод GetLastLine – для получения последней строки шаблона, а также метод EncodeString – данный метод будет кодировать переданный в его текст в зависимости от реализуемого шаблона. Также добавим виртуальный метод GetTemplate, который будет принимать текст и возвращать итоговый, получившийся шаблон.

public abstract class StringTemplate
{
    public virtual string GetTemplate(string text)
    {
        return $"{EncodeString(GetFirstLine()+text+GetLastLine())}";
    }

    public abstract string GetFirstLine();
    public abstract string EncodeString(string text);
    public abstract string GetLastLine();
}

Добавим класс SecretDocumentTemplate, который будет отвечать за формирование шаблона секретного документа. Наследуем его от класса StringTemplate и имплиментируем все необходимые методы. В методе GetFistLIne будем возвращать строку “Top Secret”. В GetLastLIne текущую дату и время. В методе EncodeString будем конвертировать строку в Base64. Также добавим метод DecodeString, для раскодирования строки, чтобы проверить всё ли корректно упаковалось в Base64.

class SecretDocumentTemplate: StringTemplate
{
    public override string GetFirstLine()
    {
        return "TOP SECRET\r\n";
    }

    public override string EncodeString(string text)
    {
        var plainTextBytes = Encoding.UTF8.GetBytes(text);
        return Convert.ToBase64String(plainTextBytes);
    }

    public override string GetLastLine()
    {
        return $"\r\nDate: {DateTime.UtcNow}";
    }

    public string DecodeString(string text)
    {
        var base64EncodedBytes = Convert.FromBase64String(text);
        return Encoding.UTF8.GetString(base64EncodedBytes);
    }
}

Теперь добавим класс HtmlTemplate, который так же наследуем от класса StringTemplate и имплементируем все необходимые методы. Добавим переменную _htmlPage для хранения базового HTML шаблона и переменную _title, для хранений заголовка страницы. Также добавим конструктор, который будет принимать значение для _title. В методе GetFirstLine будем выводить заголовок страницы, а в методе GetLastLIne дату и время в теге p. В методе EncodeString будем вызывать метод HtmlEncode для кодирования потенциально опасных символов в их HTML эквиваленты. После этого перейдём к тестированию.

public class HtmlTemplate: StringTemplate
{
    private readonly string _htmlPage = "<!DOCTYPE html><html><body >{0}</body></html>";
    private readonly string _title;

    public HtmlTemplate(string title)
    {
        _title = title;
    }

    public override string GetFirstLine()
    {
        return $"<h1>{_title}</h1>\n";
    }

    public override string EncodeString(string text)
    {
        return HttpUtility.HtmlEncode(string.Format(_htmlPage, $"<div>{text}</div>"));
    }

    public override string GetLastLine()
    {
        return $"<p>{DateTime.UtcNow}</p>";
    }
}

Теперь добавим метод EncodeString, который будет делать раскодирование строки. Метод добавим, как расширение. Создадим класс HtmlTemplateExtensions, пометим его как static. Добавим статический метод DecodeString, принимающий класс HtmlTemplate и текст для раскодирования и возвращающий текст. Здесь нужно обратить внимание что класс HtmlTemplate должен быть помечен как public если ваше расширение помечено public. А минимальное ограничение, которое вы можете использовать это internal.

public static class HtmlTemplateExtensions
{
    public static string DecodeString(this HtmlTemplate templace, string text)
    {
        return HttpUtility.HtmlDecode(text);
    }
}

Теперь вы можете вызывать метод DecodeString так, словно он был добавлен непосредственно в класс.

В .NET паттерн "Шаблонный Метод" является очень распространённым, так как любой абстрактный класс, содержащий абстрактные методы будет являться примером паттерна "Шаблонный метод". Полезным инструментом для тех, кто пишет на C# является наличие расширений, которые также можно считать примером паттерна "Шаблонный метод". Также не нужно забывать о делегатах, ведь их использование порой может сильно упростить ваш код.

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

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