Сто паттернов для разработки корпоративных программ. Часть 2.1

В этой статье рассматриваются такие дополнительные паттерны, которые не вошли в «Банду четырёх», но применяются в разработке приложений. К ним можно отнести такие паттерны, как «Abstract Document», «Active Object», «Acyclic Visitor», «Balking», «Bytecode», «Caching», «Callback», «Async Method Invocation», «Context Object», «Converter», «Data Access Object», «Data Transfer Object», «Dirty Flag», «Double Buffer», «Event Queue», «Execute Around», «Monad», «Mono State», «Mute Idiom», «Naked Objects», «Null Object», «Object Mother», «Object Pool», «Parameter Object» и многие другие.

Абстрактный документ (Abstract Document)

Паттерн «Абстрактный документ» — это структурный паттерн, который реализует обработку иерархических и древовидных структур для документов с разными типами. Он позволяет прикреплять к объектам свойства без их ведома.

Паттерн использует концепцию свойств, чтобы обеспечить безопасность типов и разделить свойства разных классов на наборы интерфейсов.

Абстрактный документ

Абстрактный документ

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

Определим общий интерфейс, а также базовую реализацию таких действий, как добавление пары «Строка-Объект», получение объекта по ключу и получение дочерних элементов.

Определение интерфейса и операций

public interface IDocument
{
    public void Put(string key, object value);

    public object Get(string key);

    public List> Children(string key);
}

public abstract class AbstractDocument : IDocument
{
    protected Dictionary Dictionary;

    protected AbstractDocument(Dictionary keyValuePairs)
    {
        this.Dictionary = keyValuePairs;
    }

    public List> Children(string key)
    {
        List> results = new();

        if (Get(key) is not List> childrens) 
        {
            return results;
        }

        foreach (Dictionary children in childrens)
        {
            results.Add(children);
        }

        return results;
    }

    public object Get(string key)
    {
        return Dictionary[key];
    }

    public void Put(string key, object value)
    {
        Dictionary.Add(key, value);
    }
}

Определим перечисление Property и набор интерфейсов для основных характеристик. Это позволит реализовать любой тип документа.

Определение перечисления и интерфейсов

public enum Property
{
    Parts, Chars, Rows, Colls, Type, Version
}

public interface IParts : IDocument
{
    public object GetParts();
}

public interface IChars : IDocument
{
    public int GetChars();
}

public interface IType : IDocument
{
    public string GetType();
}

public interface IRows : IDocument
{
    public int GetRows();
}

public interface IColls : IDocument
{
    public int GetColls();
}

public interface IVersion : IDocument
{
    public int GetVersion();
}

Определим разные типы документов с помощью AbstractDocument и тех интерфейсов, которые были заданы выше.

Определение реализации документов

public class TextDocument : AbstractDocument, 
  IParts, IChars, IType, IVersion
{
    public TextDocument(Dictionary keyValuePairs) 
      : base(keyValuePairs)
    {
        this.Dictionary = keyValuePairs;
    }

    public int GetChars()
    {
        return (int)this.Dictionary[Property.Chars.ToString()];
    }

    public object GetParts()
    {
        return this.Dictionary[Property.Parts.ToString()];
    }

    public int GetVersion()
    {
        return (int)this.Dictionary[Property.Version.ToString()];
    }

    public new string GetType()
    {
        return (string)this.Dictionary[Property.Type.ToString()];
    }
}

public class TableDocument : AbstractDocument, 
  IType, IRows, IColls, IVersion
{
    public TableDocument(Dictionary keyValuePairs) 
      : base(keyValuePairs)
    {
        this.Dictionary = keyValuePairs;
    }

    public new string GetType()
    {
        return (string)this.Dictionary[Property.Type.ToString()];
    }

    public int GetRows()
    {
        return (int)this.Dictionary[Property.Rows.ToString()];
    }

    public int GetColls()
    {
        return (int)this.Dictionary[Property.Colls.ToString()];
    }

    public int GetVersion()
    {
        return (int)this.Dictionary[Property.Version.ToString()];
    }
}

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

Использование паттерна

private static void Main(string[] args)
{
    Dictionary tableDocumentProperties = new()
    {
        { Property.Version.ToString(), 1 },

        { Property.Rows.ToString(), 10 },

        { Property.Colls.ToString(), 5 },

        { Property.Type.ToString(), ".xslx" }
    };

    Dictionary textDocumentProperties = new()
    {
        { Property.Version.ToString(), 1 },

        { Property.Chars.ToString(), 10000 },

        { Property.Type.ToString(), ".docx" },

        {
            Property.Parts.ToString(),
            new List>() { tableDocumentProperties }
        }
    };

    TextDocument textDocument = new(textDocumentProperties);

    Console.WriteLine(textDocument.GetType());

    Console.WriteLine(textDocument.GetVersion());

    Console.WriteLine(textDocument.GetChars());

    var textDocumentParts = textDocument.GetParts() as List>;

    foreach (var part in textDocumentParts)
    {
        foreach (var item in part.Keys)
        {
            Console.WriteLine(item + " " + part[item]);
        }
    }
}

Монада (Monad)

Паттерн «Монада» — это функциональный паттерн, который гарантирует, что каждая операция выполняется вне зависимости от успеха или неудачи предыдущей операции.

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

Реализация паттерна

public class Validator
{
    private readonly T value;
    private readonly List errors = new List();

    public Validator(T value)
    {
        this.value = value;
    }

    public Validator Validate(Predicate validation, string message)
    {
        if (!validation(value))
        {
            errors.Add(new Exception(message));
        }

        return this;
    }

    public (bool, List) Get()
    {
        if (errors.Any())
        {
            List result = new List();

            foreach (Exception e in errors)
            {
                result.Add(e.Message);
            }

            return (false, result);
        }

        return (true, new List());
    }
}

public record User(string Name, string Surname, string Fathername) { }

private static void Main(string[] args)
{
    User fio = new("Иван", "Иванович", "Иванов");

    Validator validator = 
        new Validator(fio)
        .Validate(x => !string.IsNullOrEmpty(x.Name), "Name is null")
        .Validate(x => !string.IsNullOrEmpty(x.Surname), "Surname is null")
        .Validate(x => !string.IsNullOrEmpty(x.Fathername), "Fathername is null");

    (bool, List) pair = validator.Get();

    Console.WriteLine(pair.Item1);
}

Мать объектов (Object Mother)

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

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

Реализация паттерна

public class Developer
{
    private string? name;
    private string? description;
    private bool isCompany;

    public void SetName(string name)
    {
        this.name = name;
    }

    public void SetDescription(string description)
    {
        this.description = description;
    }

    public void SetIsCompany(bool flag)
    {
        this.isCompany = flag;
    }
}

public static class DeveloperMother
{
    public static Developer CreatePerson(string name, string description)
    {
        Developer person = new();

        person.SetName(name);

        person.SetDescription(description);

        person.SetIsCompany(false);

        return person;
    }

    public static Developer CreateCompany(string name, string description)
    {
        Developer person = new();

        person.SetName(name);

        person.SetDescription(description);

        person.SetIsCompany(true);

        return person;
    }
}

private static void Main(string[] args)
{
    DeveloperMother.CreatePerson("Developer X", "Mobile apps");
    DeveloperMother.CreateCompany("VALVe", "CS:GO");
}

Пул объектов (Object Pool)

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

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

Реализация паттерна

public class ObjectPool
{
    private readonly ConcurrentBag _objects;
    private readonly Func _objectGenerator;

    public ObjectPool(Func objectGenerator)
    {
        this._objectGenerator = objectGenerator ?? throw new ArgumentNullException(nameof(objectGenerator));
        this._objects = new ConcurrentBag();
    }

    public T Get()
    {
        return this._objects.TryTake(out T item) ? item : this._objectGenerator();
    }

    public void Return(T item)
    {
        this._objects.Add(item);
    }
}

public class ArrayObject
{
    public int[] Nums { get; set; }

    public ArrayObject()
    {
        this.Nums = new int[1000000];

        Random rand = new();

        for (int i = 0; i < this.Nums.Length; i++)
        {
            this.Nums[i] = rand.Next();
        }
    }

    public static double LOG(int value)
    {
        return Math.Log(value);
    }

    public static double SQRT(int value)
    {
        return Math.Sqrt(value);
    }
}

private static void Main(string[] args)
{
    CancellationTokenSource cancellationTokenSource = new();

    ObjectPool objectPool = new(() => new ArrayObject());

    Parallel.For(0, 1000000, (i, state) =>
    {
        ArrayObject example = objectPool.Get();

        Console.WriteLine(ArrayObject.SQRT(i));

        objectPool.Return(example);
        
        if (cancellationTokenSource.Token.IsCancellationRequested)
        {
            state.Stop();
        }
    });
}

Пошаговый строитель (Step Builder)

Паттерн «Пошаговый строитель» — это порождающий паттерн, который применяется для создание сложного объекта в несколько шагов. Он позволяет пользователю полностью создать объект без ошибок.

Исходный объект

public class TechniqueDB
{
    public int Id { get; set; }
    public string Date { get; set; }
    public string Image { get; set; }
    public string Title { get; set; }
    public string Subtitle { get; set; }
    public string Theme { get; set; }
    public string Author { get; set; }
    public string Algorithm { get; set; }
    public bool Removed { get; set; }

}

Реализация паттерна

public class TechniqueBuilder
{
    private readonly TechniqueDB techniqueDB;

    public TechniqueBuilder()
    {
        this.techniqueDB = new TechniqueDB();
    }

    public TechniqueBuilder SetIdentifier(int identifier)
    {
        this.techniqueDB.Id = identifier;
        return this;
    }

    public TechniqueBuilder SetDate(string date)
    {
        this.techniqueDB.Date = date;
        return this;
    }
    public TechniqueBuilder SetTitle(string title)
    {
        this.techniqueDB.Title = title;
        return this;
    }
    public TechniqueBuilder SetSubtitle(string subtitle)
    {
        this.techniqueDB.Subtitle = subtitle;
        return this;
    }

    public TechniqueBuilder SetTheme(string theme)
    {
        this.techniqueDB.Theme = theme;
        return this;
    }

    public TechniqueBuilder SetAuthor(string author)
    {
        this.techniqueDB.Author = author;
        return this;
    }

    public TechniqueBuilder SetAlgorithm(string algorithm)
    {
        this.techniqueDB.Algorithm = algorithm;
        return this;
    }

    public TechniqueBuilder SetImage(string image)
    {
        this.techniqueDB.Image = image;
        return this;
    }

    public TechniqueBuilder IsVisible
    {
        get
        {
            this.techniqueDB.Removed = false;
            return this;
        }
    }
    public TechniqueDB Build()
    {
        return this.techniqueDB;
    }
}

Применение паттерна

TechniqueBuilder builder = new();

TechniqueDB technique = 
     builder.SetIdentifier(count + 1)
    .SetTitle(this.Name)
    .SetSubtitle(this.Description)
    .SetTheme(this.Theme)
    .SetImage(this.Path)
    .SetAuthor(this.Author)
    .SetAlgorithm(this.Algorithm)
    .SetDate(date)
    .Build();

Продолжение в статье «Сто паттернов для разработки корпоративных программ. Часть 2.2».

© Habrahabr.ru