Pull to refresh

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

Level of difficultyMedium
Reading time27 min
Views16K

В этой серии статей будет рассмотрено около сотни паттернов из таких сборников, как «Паттерны объектно-ориентированного проектирования», «Шаблоны корпоративных приложений», «Паттерны игрового программирования», паттерны микросервисной архитектуры и многих других. Первая часть охватывает все паттерны из «Банды четырёх», а также такие паттерны, как «Спецификация» и «Простая фабрика».

Паттерн (шаблон)

Паттерны (шаблоны) проектирования — это способы построения программ, которые считаются хорошим тоном для разработчиков. Их использование не является обязательным, но позволяет повысить качество кода. Известно, что самодельный велосипед почти всегда хуже, чем тот велосипед, который оптимизируется месяцами, годами и десятилетиями.

Порождающие паттерны

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

Простая фабрика (Simple Factory)

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

Листы бумаги
Листы бумаги

В качестве примера можно привести фабрику по производство листов бумаги. Всего существует 8 различных форматов, а их количество практически не меняется. В этом случае применение паттерна можно оправдать.

enum PaperFormat
{
    A0, A1, A2, A3, A4, A5, A6, A7, A8
}

interface IPaper { }

class PaperA0 : IPaper { }

class PaperA1 : IPaper { }

class PaperA2 : IPaper { }

class PaperA3 : IPaper { }

class PaperA4 : IPaper { }

class PaperA5 : IPaper { }

class PaperA6 : IPaper { }  

class PaperA7 : IPaper { }

class PaperA8 : IPaper { }

interface IPaperFactory
{
    IPaper CreatePaper(PaperFormat paperFormat);
}

class PaperFactory : IPaperFactory
{
    public IPaper CreatePaper(PaperFormat paperFormat)
    {
        switch (paperFormat)
        {
            case PaperFormat.A0:
                return new PaperA0();
            case PaperFormat.A1:
                return new PaperA1();
            case PaperFormat.A2:
                return new PaperA2();
            case PaperFormat.A3:
                return new PaperA3();
            case PaperFormat.A4:
                return new PaperA4();
            case PaperFormat.A5:
                return new PaperA5();
            case PaperFormat.A6:
                return new PaperA6();
            case PaperFormat.A7:
                return new PaperA7();
            case PaperFormat.A8:
                return new PaperA8();
            default: 
                return new PaperA5();
        }
    }
}

private static void Main(string[] args)
{
    IPaperFactory paperFactory = new PaperFactory();

    IPaper paperA2 = paperFactory.CreatePaper(PaperFormat.A2);
}

Фабричный метод (Factory Method)

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

Блоки в «Майнкрафт»
Блоки в «Майнкрафт»

Создание объектов в игре «Minecraft» - отличный пример. В этой игре может быть бесконечное множество различных блоков. Поэтому использование простой фабрики может сделать поддержку игры слишком тяжёлой, поскольку блоков достаточно много, а также приходится добавлять новые блоки. В этом случае гораздо удобней применить фабричный метод.

abstract class Block { }

abstract class Creator
{
    public Creator() { }

    abstract public Block Create();
}

class Ground : Block { }

class GroundCreator : Creator 
{ 
    public GroundCreator() { }

    public override Block Create()
    {
        return new Ground();
    }
}

class Wood : Block { }

class WoodCreator : Creator
{
    public WoodCreator() { }

    public override Block Create()
    {
        return new Wood();
    }
}

class Stone : Block { }

class StoneCreator : Creator
{
    public StoneCreator() { }

    public override Block Create()
    {
        return new Stone();
    }
}

class Bricks : Block { }

class BricksCreator : Creator
{
    public BricksCreator() { }

    public override Block Create()
    {
        return new Bricks();
    }
}

private static void Main(string[] args)
{
    WoodCreator woodCreator = new WoodCreator();

    Block wood = woodCreator.Create();

    StoneCreator stoneCreator = new StoneCreator();

    Block stone = stoneCreator.Create();
}

Абстрактная фабрика (Abstract Factory)

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

В качестве примера можно привести фабрику мороженого. Мороженое бывает сливочным, ванильным, шоколадным, фруктовым и ореховым. То есть как минимум пять типов. Но также мороженое может производиться разными странами: российское, китайское, тайское. Итого имеем три категории, а в каждой из них по пять типов. В этом случае удобно применить абстрактную фабрику.

abstract class IceCreamFactory
{
    public abstract Creamy GetCreamy();
    public abstract Vanilla GetVanilla();
    public abstract Chocolate GetChocolate();
    public abstract Fruit GetFruit();
    public abstract Walnut GetWalnut();
}

class RussianFactory : IceCreamFactory
{
    public override Chocolate GetChocolate()
    {
        return new RussianChocolate();
    }

    public override Creamy GetCreamy()
    {
        return new RussianCreamy();
    }

    public override Fruit GetFruit()
    {
        return new RussianFruit();
    }

    public override Vanilla GetVanilla()
    {
        return new RussianVanilla();
    }

    public override Walnut GetWalnut()
    {
        return new RussianWalnut();
    }
}

class ChineseFactory : IceCreamFactory
{
    public override Chocolate GetChocolate()
    {
        return new ChineseChocolate();
    }

    public override Creamy GetCreamy()
    {
        return new ChineseCreamy();
    }

    public override Fruit GetFruit()
    {
        return new ChineseFruit();
    }

    public override Vanilla GetVanilla()
    {
        return new ChineseVanilla();
    }

    public override Walnut GetWalnut()
    {
        return new ChineseWalnut();
    }
}

class ThaiFactory : IceCreamFactory
{
    public override Chocolate GetChocolate()
    {
        return new ThaiChocolate();
    }

    public override Creamy GetCreamy()
    {
        return new ThaiCreamy();
    }

    public override Fruit GetFruit()
    {
        return new ThaiFruit();
    }

    public override Vanilla GetVanilla()
    {
        return new ThaiVanilla();
    }

    public override Walnut GetWalnut()
    {
        return new ThaiWalnut();
    }
}

#region Abstract Items
abstract class Creamy { }

abstract class Vanilla { }

abstract class Chocolate { }

abstract class Fruit { }

abstract class Walnut { }
#endregion

#region Russian Items
class RussianCreamy : Creamy { }

class RussianVanilla : Vanilla { }

class RussianChocolate : Chocolate { }

class RussianFruit : Fruit { }

class RussianWalnut : Walnut { }
#endregion

#region Chinese Items
class ChineseCreamy : Creamy { }

class ChineseVanilla : Vanilla { }

class ChineseChocolate : Chocolate { }

class ChineseFruit : Fruit { }

class ChineseWalnut : Walnut { }
#endregion

#region Thai Items
class ThaiCreamy : Creamy { }

class ThaiVanilla : Vanilla { }

class ThaiChocolate : Chocolate { }

class ThaiFruit : Fruit { }

class ThaiWalnut : Walnut { }
#endregion

class IceCreamManager
{
    public IceCreamFactory GetRussianFactory()
    {
        return new RussianFactory();
    }

    public IceCreamFactory GetChineseFactory()
    {
        return new ChineseFactory();
    }

    public IceCreamFactory GetThaiFactory()
    {
        return new ThaiFactory();
    }
}

private static void Main(string[] args)
{
    IceCreamManager manager = new IceCreamManager();

    IceCreamFactory russianFactory = manager.GetRussianFactory();
}

Строитель (Builder)

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

Паттерн может быть применён для создания сообщений, поскольку оно состоит из таких частей, как текст, форма, изображение, продолжительность.

private enum MessageForm
{
    Square,
    Circle
}

private abstract class AbstractBuilder
{
    public abstract void SetText(string text);
    public abstract void SetImage(string image);
    public abstract void SetLength(int length);
    public abstract void SetForm(MessageForm form);

    public abstract Message Build();
}

private class Message
{
    public string Text { get; set; }
    public string Image { get; set; }
    public int Length { get; set; }
    public MessageForm Form { get; set; }
}

private class MessageBuilder : AbstractBuilder
{
    private readonly Message message = new Message();

    public override void SetForm(MessageForm form)
    {
        this.message.Form = form;
    }

    public override void SetImage(string image)
    {
        this.message.Image = image;
    }

    public override void SetLength(int length)
    {
        this.message.Length = length;
    }

    public override void SetText(string text)
    {
        this.message.Text = text;
    }

    public override Message Build()
    {
        return this.message;
    }
}

private static void Main(string[] args)
{
    AbstractBuilder builder = new MessageBuilder();

    builder.SetText("Новое сообщение");

    builder.SetLength(2);

    Message message = builder.Build();
}

Прототип (Prototype)

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

interface IBlock
{
    int Side { get; set; }
    IBlock Clone();
}

class Water : IBlock
{
    public Water(Color color, int side)
    {
        this.Color = color;
        this.Side = side;
    }

    public Color Color { get; set; }

    public int Side { get; set; }

    public IBlock Clone()
    {
        return new Water(this.Color, this.Side);
    }

    public override string ToString()
    {
        return $"Цвет - {this.Color}; Сторона - {this.Side}";
    }
}

class Air : IBlock
{
    public Air(int side)
    {
        this.Side = side;
    }

    public int Side { get; set; }

    public IBlock Clone()
    {
        return new Air(this.Side);
    }

    public override string ToString()
    {
        return $"Сторона - {this.Side}";
    }
}

private static void Main(string[] args)
{
    IBlock water = new Water(new Color(), 10);

    IBlock waterCloned = water.Clone();
}

Одиночка (Singleton)

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

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

class World
{
    private static World instance;

    private static object instance_lock = new object();
    public string Name { get; private set; }
    public int Version { get; private set; }

    protected World(string name, int version)
    {
        this.Name = name;
        this.Version = version;
    }

    public static World GetInstance(string name, int version)
    {
        lock (instance_lock)
        {
            if (instance == null)
            {
                instance = new World(name, version);
            }
        }

        return instance;
    }
}


private static void Main()
{
    World world = World.GetInstance("Мой мир", 1);

    Console.WriteLine(world.Name);

    Console.Read();
}

Паттерны поведения

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

Стратегия (Strategy)

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

Например, в приложении имеется логирование, которое работает для таких форматов, как txt, xml и json. Во всех трёх случаях суть одна, но реализации для каждого из них существенно отличаются. В этом случае удобно реализовать абстракцию к логированию, а к ней три реализации для соответствующих форматов.

interface ILogger
{
    void Log();
}

class TxtLogger : ILogger
{
    // Implementation of logging in txt format
    public void Log() { } 
}

class XmlLogger : ILogger
{
    // Implementation of logging in xml format
    public void Log() { } 
}

class JsonLogger : ILogger
{
    // Implementation of logging in json format
    public void Log() { } 
}

class Context
{
    public ILogger Logger { get; set; }

    public Context(ILogger logger)
    {
        this.Logger = logger;
    }

    public void ExecuteLogging()
    {
        this.Logger.Log();
    }
}

private static void Main(string[] args)
{
    ILogger logger = new XmlLogger();

    Context context = new Context(logger);

    context.ExecuteLogging();
}

Наблюдатель (Observer)

Паттерн «Наблюдатель» - это поведенческий паттерн, который реализует механизм «Подписки-отписки». Он организует взаимодействие объектов по принципу «Один ко многим». Классическим примером является социальная сеть «YouTube», которая включает в себя как блогеров, так и подписчиков.

Паттерн используется, когда:

  • система состоит из множества объектов, где все объекты должны находиться в согласованных состояниях

  • имеется объект, который осуществляет рассылку, и объекты, объекты, которые получают эту рассылку.

private interface IObservable
{
    void AddObserver(IObserver observer);
    void RemoveObserver(IObserver observer);
    void NotifyObservers();
}

private interface IObserver
{
    void Update();
}

private class Subscriber : IObserver
{
    public void Update()
    {
        // Update profile of subscriber
    }
}

private class Blogger : IObservable
{
    private readonly List<IObserver> observers;

    public Blogger()
    {
        this.observers = new List<IObserver>();
    }

    public void AddObserver(IObserver observer)
    {
        this.observers.Add(observer);
    }

    public void NotifyObservers()
    {
        foreach (IObserver observer in this.observers)
        {
            observer.Update();
        }
    }

    public void RemoveObserver(IObserver observer)
    {
        _ = this.observers.Remove(observer);
    }
}

private static void Main(string[] args)
{
    IObserver subscriberA = new Subscriber();

    IObserver subscriberB = new Subscriber();

    IObservable blogger = new Blogger();

    blogger.AddObserver(subscriberA);

    blogger.AddObserver(subscriberB);

    blogger.NotifyObservers();
}

Команда (Command)

Паттерн «Команда» - это поведенческий паттерн, который инкапсулирует выполнение действия в виде отдельного объекта.

Паттерн используется в следующих случаях:

  • необходимо обеспечить выполнение очереди запросов, а также возможность их отмены,

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

  • необходимо логгировать изменения в результате запросов, а это может быть полезно для восстановления системы

private abstract class Command
{
    public abstract void Run();
    public abstract void Back();
}

private class MatrixCommand : Command
{
    private readonly char operation;
    private readonly int[][] matrix;
    private readonly MatrixCalculator calculator;

    public MatrixCommand(char operation, int[][] matrix, MatrixCalculator calculator)
    {
        this.operation = operation;
        this.matrix = matrix;
        this.calculator = calculator;
    }

    public override void Back()
    {
        char action = Undo(this.operation);

        this.calculator.Operation(action, this.matrix);
    }

    public override void Run()
    {
        this.calculator.Operation(this.operation, this.matrix);
    }

    private char Undo(char operation)
    {
        switch (operation)
        {
            case '+':
                return '-';
            case '-':
                return '+';
            default:
                throw new ArgumentException();
        }
    }
}

private class MatrixCalculator
{
    private readonly int[][] matrixA;

    public MatrixCalculator(int rows, int cols)
    {
        this.matrixA = new int[rows][];

        for (int row = 0; row < rows; row++)
        {
            this.matrixA[row] = new int[cols];
        }
    }

    private void Operation(int[][] matrixB, Func<int, int, int> func)
    {
        for (int row = 0; row < matrixB.Length; row++)
        {
            for (int col = 0; col < matrixB[0].Length; col++)
            {
                this.matrixA[row][col] = func(this.matrixA[row][col], matrixB[row][col]);
            }
        }
    }

    public void Operation(char operation, int[][] matrixB)
    {
        switch (operation)
        {
            case '+':
                Operation(matrixB, (x, y) => x + y);
                break;
            case '-':
                Operation(matrixB, (x, y) => x - y);
                break;
        }
    }
}

public class Client
{
    private readonly MatrixCalculator calculator;
    private readonly List<Command> commands; 
    private int current;

    public Client(int rows, int cols)
    {
        this.calculator = new MatrixCalculator(rows, cols);
        this.commands = new List<Command>();
        this.current = 0;
    }

    public void Compute(char operation, int[][] matrix)
    {
        Command command = new MatrixCommand(operation, matrix, this.calculator);

        command.Run();

        this.commands.Add(command);

        this.current++;
    }

    public void Back()
    {
        Command command = this.commands[--this.current];

        command.Back();
    }
}

static void Main()
{
    Client client = new Client(2, 2);

    int[][] matrix = new int[2][]
    {
        new int[2] { 1, 0 },
        new int[2] {0, 4}
    };

    client.Compute('+', matrix);

    client.Back();
}

Шаблонный метод (Template Method)

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

Паттерн используется в случаях, когда

  • отдельные шаги алгоритма могут быть переопределены,

  • при реализации подобных алгоритмов происходит дублирование кода

abstract class Profile
{
    public void Init()
    {
        GetData();
        SaveData();
        ShowData();
    }

    public virtual void GetData() 
    {
        Console.WriteLine("Обращаемся к API");
    }

    public virtual void SaveData() 
    {
        Console.WriteLine("Сохраняем в локальную базу данных");
    }

    public virtual void ShowData() 
    {
        Console.WriteLine("Подготавливаем пользовательский интерфейс");
    }
}

// При необходимости переопределяем методы для гостя
class Guest : Profile { }

// При необходимости переопределяем методы для админа
class Admin : Profile { }

static void Main()
{
    
}

Итератор (Iterator)

Паттерн «Итератор» - это паттерн, который реализует перечисление всех элементов объекта без раскрытия его внутренней структуры.

private interface IProductIterator
{
    bool HasNext();
    Product Next();
}

private interface IProductNumerable
{
    IProductIterator GetProductNumerator();
    int Count { get; }
    Product this[int index] { get; }
}

private class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

private class Shop : IProductNumerable
{
    private readonly Product[] products;
    public Shop()
    {
        this.products = new Product[]
        {
            new Product { Id = 1, Name = "Компьютер"},
            new Product { Id = 2, Name = "Провод"}
        };
    }

    public Product this[int index] => this.products[index];

    public int Count => this.products.Length;

    public IProductIterator GetProductNumerator()
    {
        return new ShopNumerator(this);
    }
}

private class ShopNumerator : IProductIterator
{
    private readonly IProductNumerable productNumerable;
    private int index = 0;

    public ShopNumerator(IProductNumerable productNumerable)
    {
        this.productNumerable = productNumerable;
    }

    public bool HasNext()
    {
        return this.index < this.productNumerable.Count;
    }

    public Product Next()
    {
        return this.productNumerable[this.index++];
    }
}

private static void Main()
{
    Shop shop = new Shop();

    IProductIterator iterator = shop.GetProductNumerator();

    while (iterator.HasNext())
    {
        Console.WriteLine(iterator.Next().Name);
    }

    Console.Read();
}

Состояние (State)

Паттерн «Состояние» - это поведенческий паттерн, в котором объект меняет своё поведение в зависимости от состояния.

Паттерн используется, когда:

  • поведение должно меняться в соответствии с состоянием

  • имеется множество условных конструкций, которые усложняют поддержку кода

enum PageState
{
    Done,
    Null,
    Error,
    Load
}

abstract class State
{
    public abstract void Handle(Page page);
}

class Done : State
{
    public override void Handle(Page page)
    {
        // Данные успешно загружены. Показываем загруженную страницу
        Console.WriteLine("Страница загружена");
    }
}

class Null : State
{
    public override void Handle(Page page)
    {
        // Исключений нет, но данных нет. Показываем предупреждение
        Console.WriteLine("Нет данных");
    }
}

class Error : State
{
    public override void Handle(Page page)
    {
        // В ходе выполнения произошли исключения. Показываем предупреждение
        Console.WriteLine("Ошибка");
    }
}

class Load : State
{
    public override void Handle(Page page)
    {
        // Загружаем данные. Показываем анимацию загрузки
        Console.WriteLine("Загрузка страницы");
        page.State = new Done();
    }
}

class Page
{
    public State State { get; set; }
    public Page(State state)
    {
        this.State = state;
    }

    public void Request()
    {
        this.State.Handle(this);
    }
}

private static void Main()
{
    Page page = new Page(new Load());

    page.Request();

    page.Request();

    Console.Read();
}

Цепочка обязанностей (Chain of responsibility)

Паттерн «Цепочка обязанностей» - это поведенческий паттерн, который позволяет избежать жёсткой привязки между получателем и отправителем. Суть в том, что каждый узел либо обрабатывает запрос, либо передаёт его следующему узлу.

Паттерн применяется, когда:

  • имеется несколько объектов, который могут обработать запрос,

  • набор объектов задаётся динамически

  • необходимо передать запрос на выполнение одному из имеющихся объектов

class Receiver
{
    public bool OpenWithWord { get; set; }

    public bool OpenWithNotepad { get; set; }
    public bool OpenWithPdfReader { get; set; }

    public bool OpenWithGoogleChrome { get; set; }

    public Receiver(bool openWithWord, bool openWithNotepad, bool openWithPdfReader, bool openWithGoogleChrome)
    {
        this.OpenWithWord = openWithWord;
        this.OpenWithNotepad = openWithNotepad;
        this.OpenWithPdfReader = openWithPdfReader;
        this.OpenWithGoogleChrome = openWithGoogleChrome;
    }
}
abstract class FileReader
{
    public FileReader Successor { get; set; }
    public abstract void Handle(Receiver receiver);
}

class WordReader : FileReader
{
    public override void Handle(Receiver receiver)
    {
        if (receiver.OpenWithWord == true)
            Console.WriteLine("Открываем файл в Word");
        else if (Successor != null)
            Successor.Handle(receiver);
    }
}

class NotepadReader : FileReader
{
    public override void Handle(Receiver receiver)
    {
        if (receiver.OpenWithNotepad == true)
            Console.WriteLine("Открываем файл в Notepad");
        else if (Successor != null)
            Successor.Handle(receiver);
    }
}

class PdfReader : FileReader
{
    public override void Handle(Receiver receiver)
    {
        if (receiver.OpenWithPdfReader == true)
            Console.WriteLine("Открываем файл в PDF Reader");
        else if (Successor != null)
            Successor.Handle(receiver);
    }
}

class GoogleChrome : FileReader
{
    public override void Handle(Receiver receiver)
    {
        if (receiver.OpenWithGoogleChrome == true)
            Console.WriteLine("Открываем файл в Google Chrome");
        else if (Successor != null)
            Successor.Handle(receiver);
    }
}

private static void Main()
{
    Receiver receiver = new Receiver(false, false, true, false);

    WordReader wordReader = new WordReader();

    NotepadReader notepadReader = new NotepadReader();

    PdfReader pdfReader = new PdfReader();

    GoogleChrome googleChrome = new GoogleChrome();

    wordReader.Successor = notepadReader;

    notepadReader.Successor = pdfReader;

    pdfReader.Successor = notepadReader;

    wordReader.Handle(receiver);

    Console.Read();
}

Интерпретатор (Interpreter)

Паттерн «Интерпретатор» - это поведенческий паттерн, который реализует обработку синтаксических конструкций. Обычно он используется для тех операций, которые часто повторяются.

Паттерн используется, когда:

  • имеется много операций, которые часто повторяются

  • необходимо разработать интерпретатор для обработки языка

private class Context
{
    private readonly Dictionary<string, int> variables;
    public Context()
    {
        this.variables = new Dictionary<string, int>();
    }
    public int GetVariable(string name)
    {
        return this.variables[name];
    }

    public void SetVariable(string name, int value)
    {
        if (this.variables.ContainsKey(name))
        {
            this.variables[name] = value;
        }
        else
        {
            this.variables.Add(name, value);
        }
    }
}

private interface IExpression
{
    int Interpret(Context context);
}

private class VariableExpression : IExpression
{
    private readonly string name;
    public VariableExpression(string variableName)
    {
        this.name = variableName;
    }
    public int Interpret(Context context)
    {
        return context.GetVariable(this.name);
    }
}

private class FactorialExpression : IExpression
{
    private readonly string name;
    public FactorialExpression(string variableName)
    {
        this.name = variableName;
    }

    public int Interpret(Context context)
    {
        int sum = new int();

        int value = context.GetVariable(this.name);

        sum++;

        for (int i = 2; i <= value; i++)
        {
            sum *= i;
        }

        return sum;
    }
}

private class LogExpression : IExpression
{
    private readonly IExpression bottomExpression;
    private readonly IExpression topExpression;

    public LogExpression(IExpression bottomExpression, IExpression topExpression)
    {
        this.bottomExpression = bottomExpression;
        this.topExpression = topExpression;
    }

    public int Interpret(Context context)
    {
        return (int)Math.Log(this.topExpression.Interpret(context), this.bottomExpression.Interpret(context));
    }
}

private class AddExpression : IExpression
{
    private readonly IExpression leftExpression;
    private readonly IExpression rightExpression;

    public AddExpression(IExpression left, IExpression right)
    {
        this.leftExpression = left;
        this.rightExpression = right;
    }

    public int Interpret(Context context)
    {
        return this.leftExpression.Interpret(context) + this.rightExpression.Interpret(context);
    }
}

private class SubtractExpression : IExpression
{
    private readonly IExpression leftExpression;
    private readonly IExpression rightExpression;

    public SubtractExpression(IExpression left, IExpression right)
    {
        this.leftExpression = left;
        this.rightExpression = right;
    }

    public int Interpret(Context context)
    {
        return this.leftExpression.Interpret(context) - this.rightExpression.Interpret(context);
    }
}

private class MultiplyExpression : IExpression
{
    private readonly IExpression leftExpression;
    private readonly IExpression rightExpression;

    public MultiplyExpression(IExpression leftExpression, IExpression rightExpression)
    {
        this.leftExpression = leftExpression;
        this.rightExpression = rightExpression;
    }

    public int Interpret(Context context)
    {
        return this.leftExpression.Interpret(context) * this.rightExpression.Interpret(context);
    }
}

private class DivideExpression : IExpression
{
    private readonly IExpression leftExpression;
    private readonly IExpression rightExpression;

    public DivideExpression(IExpression leftExpression, IExpression rightExpression)
    {
        this.leftExpression = leftExpression;
        this.rightExpression = rightExpression;
    }

    public int Interpret(Context context)
    {
        return this.leftExpression.Interpret(context) / this.rightExpression.Interpret(context);
    }
}

private static void Main()
{
    Context context = new Context();

    int x = 10;
    int y = 5;
    int z = 3;
    int w = 15;

    context.SetVariable("x", x);
    context.SetVariable("y", y);
    context.SetVariable("z", z);
    context.SetVariable("w", w);

    IExpression expression = new AddExpression(
        new LogExpression(new VariableExpression("y"), new VariableExpression("w")),
        new VariableExpression("z"));

    Console.WriteLine(expression.Interpret(context));

    Console.Read();
}

Хранитель (Memento)

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

Паттерн применяет в случаях, когда:

  • необходимо сохранить состояние и возможность его восстановить

  • необходимо сохранить состояние без нарушения принципа инкапсуляции

private class Player
{
    private string name = "Stieve";
    private int health = 100;

    public void Upgrade()
    {
        this.health += 50;
    }

    public PlayerMemento SaveState()
    {
        return new  PlayerMemento(name, health);
    }

    public void RestoreState(PlayerMemento memento)
    {
        this.name = memento.Name;
        this.health = memento.Health;
    }
}

private class PlayerMemento
{
    public string Name { get; private set; }
    public int Health { get; private set; }

    public PlayerMemento(string name, int health)
    {
        this.Name = name;
        this.Health = health;
    }
}

private class GameHistory
{
    public List<PlayerMemento> History { get; private set; }

    public GameHistory()
    {
        this.History = new List<PlayerMemento>();
    }
}

private static void Main()
{
    Player player = new Player();

    GameHistory history = new GameHistory();

    history.History.Add(player.SaveState());

    player.Upgrade();

    player.RestoreState(history.History.Last());

    Console.Read();
}

Посредник (Mediator)

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

Паттерн следует использовать в случаях, когда:

  • имеется множество объектов, а связи между ними сложны и запутаны

  • необходимо повторно использовать объект, но это осложняется связями с другими объектами

private abstract class Mediator
{
    public abstract void Send(string message, Colleague colleague);
}

private abstract class Colleague
{
    protected Mediator mediator;

    public Colleague(Mediator mediator)
    {
        this.mediator = mediator;
    }

    public virtual void Send(string message)
    {
        this.mediator.Send(message, this);
    }
    public abstract void Notify(string message);
}

private class Manager : Mediator
{
    public Colleague Customer { get; set; }
    public Colleague Leader { get; set; }
    public Colleague Architector { get; set; }
    public Colleague BuildersTeam { get; set; }
    public override void Send(string message, Colleague colleague)
    {
        if (this.Customer == colleague)
        {
            this.Leader.Notify(message);
        }

        else if (this.Leader == colleague)
        {
            this.Architector.Notify(message);
        }

        else if (this.Architector == colleague)
        {
            this.BuildersTeam.Notify(message);
        }

        else if (this.BuildersTeam == colleague)
        {
            this.Customer.Notify(message);
        }
    }
}

private class Customer : Colleague
{
    public Customer(Mediator mediator) : base(mediator)
    {
    }

    public override void Notify(string message)
    {
        Console.WriteLine("Сообщение для руководителя: " + message);
    }
}

private class Leader : Colleague
{
    public Leader(Mediator mediator) : base(mediator)
    {
    }

    public override void Notify(string message)
    {
        Console.WriteLine("Сообщение для архитектора: " + message);
    }
}

private class Architector : Colleague
{
    public Architector(Mediator mediator) : base(mediator)
    {

    }

    public override void Notify(string message)
    {
        Console.WriteLine("Сообщение для строителя: " + message);
    }
}

private class BuildersTeam : Colleague
{
    public BuildersTeam(Mediator mediator) : base(mediator)
    {
    }

    public override void Notify(string message)
    {
        Console.WriteLine("Сообщение для заказчика: " + message);
    }
}

private static void Main()
{
    Manager manager = new Manager();

    Colleague customer = new Customer(manager);
    Colleague leader = new Leader(manager);
    Colleague architector = new Architector(manager);
    Colleague builders = new BuildersTeam(manager);

    manager.Customer = customer;
    manager.Leader = leader;
    manager.Architector = architector;
    manager.BuildersTeam = builders;

    customer.Send("Сделать парк");

    leader.Send("Спроектировать парк");

    architector.Send("Построить парк");

    builders.Send("Парк готов");

    Console.Read();
}

Посетитель (Visitor)

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

При использовании паттерна определяются две иерархии классов. Одна указывает на операцию, а другая реализует эту операцию.

// Абсракция посетителя
interface IVisitor
{
    void Serialize(ILog log);
}

// Посетитель, который реализует сериализацию в html
class HtmlSerializer : IVisitor
{
    public void Serialize(ILog log)
    {
        // Serialize text to html
    }
}

// Посетитель, который реализует сериализацию в xml
class XmlSerializer : IVisitor
{
    public void Serialize(ILog log)
    {
        // Serialize text to xml
    }
}

// Посетитель, который реализует сериализацию в json
class JsonSerializer : IVisitor
{
    public void Serialize(ILog log)
    {
        // Serialize text to json
    }
}

// Абстракция объекта
interface ILog
{
    string Location { get; set; }
    string Message { get; set; }
    string DateTime { get; set; }
    string StackTrace { get; set; }
    void Log(IVisitor visitor);
}

// Реализация объекта
class Error : ILog
{
    public bool IsFatal { get; set; }
    public string Location { get; set; }
    public string Message { get; set; }
    public string DateTime { get; set; }
    public string StackTrace { get; set; }

    public void Log(IVisitor visitor) 
    { 
        visitor.Serialize(this);
    }
}

// Реализация объекта
class Warn : ILog
{
    public string Location { get; set; }
    public string Message { get; set; }
    public string DateTime { get; set; }
    public string StackTrace { get; set; }

    public void Log(IVisitor visitor)
    {
        visitor.Serialize(this);
    }
}

// Реализация объекта
class Info : ILog
{
    public string Location { get; set; }
    public string Message { get; set; }
    public string DateTime { get; set; }
    public string StackTrace { get; set; }

    public void Log(IVisitor visitor)
    {
        visitor.Serialize(this);
    }
}

// Некоторая структура, которая предоставляет доступ к объектам
class Logger
{
    List<ILog> logs = new List<ILog>();
    public void Add(ILog log)
    {
        logs.Add(log);
    }

    public void Remove(ILog log)
    {
        logs.Remove(log);
    }

    public void Log(IVisitor visitor)
    {
        foreach (ILog log in logs)
        {
            log.Log(visitor);
        }
    }
}

private static void Main()
{
    Logger logger = new Logger();

    logger.Add(new Warn());

    logger.Add(new Info());

    logger.Log(new XmlSerializer());

    Console.Read();
}

Структурные паттерны

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

Декоратор (Decorator)

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

Паттерн хорошо применяется в случаях, когда:

  • необходимо к объекту динамически добавлять новые функции

  • наследование является нежелательным для иерархии, поскольку она уже разрослась

abstract class Phone
{
    public string Name { get; protected set; }

    public Phone(string name)
    {
        this.Name = name;
    }

    public abstract int GetCost();
}

class Smartphone : Phone
{
    public Smartphone() : base("Телефон с ОС Android")
    {
    }

    public override int GetCost()
    {
        return 5000;
    }
}

class Iphone : Phone
{
    public Iphone() : base("Телефон с ОС IOS")
    {
    }

    public override int GetCost()
    {
        return 15000;
    }
}

abstract class PhoneDecorator : Phone
{
    protected Phone phone;
    protected PhoneDecorator(string name, Phone phone) : base(name)
    {
        this.phone = phone;
    }
}

class PhoneWithMemoryCard : PhoneDecorator
{
    public PhoneWithMemoryCard(Phone phone) : base(phone.Name + " с картой памяти", phone)
    {
    }

    public override int GetCost()
    {
        return phone.GetCost() + 1000;
    }
}

class PhoneWithCover : PhoneDecorator
{
    public PhoneWithCover(Phone phone) : base(phone.Name + " с чехлом", phone)
    {
    }

    public override int GetCost()
    {
        return phone.GetCost() + 500;
    }
}

private static void Main()
{
    Phone smartphone = new Smartphone();

    smartphone = new PhoneWithCover(smartphone);

    smartphone = new PhoneWithMemoryCard(smartphone);

    Console.WriteLine(smartphone.GetCost());

    Console.Read();
}

Адаптер (Adapter)

Паттерн «Адаптер» - это структурный паттерн, который решает проблему с совместимостью. Как пример можно привести советскую вилку и европейскую розетку. Сами по себе они несоединимы. Но соответствующий адаптер легко решает эту проблему.

Существует две основные реализации:

  • унаследовать класс А от класса В или наоборот, чтобы добиться совместимости,

  • использовать класс А в классе В как переменную или наоборот, чтобы добиться совместимости

private interface IDetectorA
{
    bool Detect(int value);
}

private class DetectorA : IDetectorA
{
    public bool Detect(int value)
    {
        return value > 0;
    }
}

private enum Result
{
    High,
    Medium,
    Low,
    Null
}

private interface IDetectorB
{
    Result Detect(int value);
}

private class DetectorB : IDetectorB
{
    public Result Detect(int value)
    {
        if (value <= 0)
        {
            return Result.Null;
        }

        else if (value <= 10)
        {
            return Result.Low;
        }

        else
        {
            return value <= 20 ? Result.Medium : Result.High;
        }
    }
}

private class Adapter : IDetectorA
{
    private readonly DetectorB detectorB;

    public Adapter(DetectorB detectorB)
    {
        this.detectorB = detectorB;
    }

    public bool Detect(int value)
    {
        Result result = this.detectorB.Detect(value);

        return result != Result.Null;
    }
}

private static void Main()
{
    DetectorB detectorB = new DetectorB();

    Adapter adapter = new Adapter(detectorB);

    adapter.Detect(0);

    _ = Console.Read();
}

Фасад (Facade)

Паттерн «Фасад» - это структурный паттерн, который позволяет скрыть сложность системы от простого пользователя.

Паттерн может применяться в следующих случаях:

  • необходимо упростить работу со сложной системой,

  • необходимо уменьшить количество зависимостей между клиентом и системой,

  • необходимо определить подсистемы в сложной системе.

class RestApi
{
    public void GetData()
    {
        // Получение данных от сервера
    }
}

class SqliteDB
{
    public void SaveData()
    {
        // Сохранение данных в локальной базе данных
    }
}

class Presentation
{
    public void LoadData()
    {
        // Загрузить данные из хранилища
    }

    public void ShowContent()
    {
        // Показать пользователю контент
    }
}

class UIFacade
{
    RestApi restApi;
    SqliteDB sqliteDB;
    Presentation presentation;

    public UIFacade(RestApi restApi, SqliteDB sqliteDB, Presentation presentation)
    {
        this.restApi = restApi;
        this.sqliteDB = sqliteDB;
        this.presentation = presentation;
    }

    public void Continue()
    {
        restApi.GetData();
        sqliteDB.SaveData();
        presentation.LoadData();
        presentation.ShowContent();
    }
}

class User
{
    public void OpenPage(UIFacade facade) 
    {
        facade.Continue();
    }
}

Компоновщик (Composite)

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

Паттерн используется в следующих случаях:

  • необходимо работать с объектами на уровне древовидной структуры,

  • необходимо работать как с группами объектов, так и с отдельными объектами.

 public abstract class Component
    {
        protected string name;

        public Component(string name)
        {
            this.name = name;
        }

        public virtual void Add(Component component)
        {

        }

        public virtual void Remove(Component component)
        {

        }

        public override string ToString()
        {
            return this.name;
        }
    }

    internal class Folder : Component
    {
        private List<Component> components = new();

        public Folder(string name) : base(name)
        {

        }

        public List<Component> GetComponents()
        {
            return this.components;
        }

        public FolderMemento SaveState()
        {
            return new FolderMemento(this.name, this.components);
        }

        public void RestoreState(FolderMemento memento)
        {
            this.name = memento.Name;
            this.components = memento.Components;
        }

        public override void Add(Component component)
        {
            this.components.Add(component);
        }

        public override void Remove(Component component)
        {
            _ = this.components.Remove(component);
        }

        public override string ToString()
        {
            List<string> list = new();

            foreach (Component component in this.components)
            {
                list.Add(component.ToString());
            }

            return string.Join(", ", list.ToArray());
        }
    }

    internal class File : Component
    {
        public File(string name) : base(name)
        {

        }
    }

Заместитель (Proxy)

Паттерн «Заместитель» - это структурный объект, который несёт ответственность за управление другим объектом.

Он используется в случаях, когда:

  • необходимо ограничить доступ к объекту в зависимости от прав доступа (Защищающий заместитель),

  • необходимо управлять доступом к объекту, создание которого требует много времени и производительности (Виртуальный заместитель),

  • необходимо реализовать работу с объектом с учётом многопоточности (Умные ссылки),

  • необходимо осуществить взаимодействие по сети и снизить накладные издержки (Удалённый заместитель).

// Абстракция объекта
public interface IMath
{
    double Add(double x, double y);
    double Sub(double x, double y);
    double Mul(double x, double y);
    double Div(double x, double y);
}

// Реализация объекта
public class Math : IMath
{
    public double Add(double x, double y) => x + y;
    public double Sub(double x, double y) => x - y;
    public double Mul(double x, double y) => x * y;
    public double Div(double x, double y) => x / y;
}

// Прокси для объекта
public class MathProxy : IMath
{
    private readonly Math math = new Math();
    public double Add(double x, double y)
    {
        return this.math.Add(x, y);
    }
    public double Sub(double x, double y)
    {
        return this.math.Sub(x, y);
    }
    public double Mul(double x, double y)
    {
        return this.math.Mul(x, y);
    }
    public double Div(double x, double y)
    {
        return this.math.Div(x, y);
    }
}

private static void Main()
{
    MathProxy proxy = new MathProxy();

    Console.WriteLine("4 + 2 = " + proxy.Add(4, 2));
    Console.WriteLine("4 - 2 = " + proxy.Sub(4, 2));
    Console.WriteLine("4 * 2 = " + proxy.Mul(4, 2));
    Console.WriteLine("4 / 2 = " + proxy.Div(4, 2));

    Console.Read();
}

Мост (Bridge)

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

Паттерн хорошо работает для тех случаев, когда нужно избежать жёсткой связи между абстракцией и реализацией, а также, если нужно менять абстракцию и реализацию независимо друг от друга.

// Абстракция красителя
public abstract class Color
{
    public abstract void PaintOver();
}

// Красный краситель
public class Red : Color
{
    public override void PaintOver()
    {
        // Окрашиваем в красный
    }
}

// Зелёный краситель
public class Green : Color
{
    public override void PaintOver()
    {
        // Окрашиваем в зелёный
    }
}

// Синий краситель
public class Blue : Color
{
    public override void PaintOver()
    {
        // Окрашиваем в синий
    }
}

// Абстракция для тротуарной плитки
public abstract class Tile
{
    protected Color color { get; set; }

    public Tile(Color color)
    {
        this.color = color;
    }

    public abstract void ApplyColor();
}

// Круглая плитка
public class RoundTile : Tile
{
    public RoundTile(Color color) : base(color)
    {
    }

    public override void ApplyColor()
    {
        this.color.PaintOver();
    }
}

// Квадратная плитка
public class SquareTile : Tile
{
    public SquareTile(Color color) : base(color)
    {
    }

    public override void ApplyColor()
    {
        this.color.PaintOver();
    }
}

// Прямоугольная плитка
public class RectangleTile : Tile
{
    public RectangleTile(Color color) : base(color)
    {
    }

    public override void ApplyColor()
    {
        this.color.PaintOver(); ;
    }
}

private static void Main()
{
    RoundTile roundTileBlue = new RoundTile(new Blue());

    RoundTile roundTileGreen = new RoundTile(new Green());

    Console.Read();
}

Приспособленец (Flyweight)

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

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

// Абстракция объекта
abstract class Symvol
{
    protected char value;

    public abstract void Write();
}

// Символ А
class SymvolA : Symvol
{
    public SymvolA()
    {
        this.value = 'A';
    }

    public override void Write()
    {
        Console.WriteLine(this.value);
    }
}

// Символ B
class SymvolB : Symvol
{
    public SymvolB()
    {
        this.value = 'B';
    }

    public override void Write()
    {
        Console.WriteLine(this.value);
    }
}

// Фабрика объектов, которые используются в разных контекстах
class SymvolFactory
{
    Dictionary<string, Symvol> symvols = new Dictionary<string, Symvol>();

    public SymvolFactory()
    {
        symvols.Add("A", new SymvolA());
        symvols.Add("B", new SymvolB());
    }

    public Symvol GetSymvol(string key)
    {
        if (symvols.ContainsKey(key))
        {
            return symvols[key];
        }

        else
        {
            return null;
        }
    }
}

static void Main()
{

    SymvolFactory symvolFactory = new SymvolFactory();

    Console.WriteLine(symvolFactory.GetSymvol("A"));

    Console.Read();
}

Дополнительные паттерны

Спецификация (Specification)

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

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

// Абстракция спецификации
public abstract class Specification<T>
{
    public abstract Expression<Func<T, bool>> ToExpression();

    public bool IsSatisfiedBy(T entity)
    {
        Func<T, bool> predicate = ToExpression().Compile();
        return predicate(entity);
    }
}

// Спецификация для валидации цитат
public class QuotValidateSpecification : Specification<Quot>
{
    public QuotValidateSpecification() 
    { 
    }

    public override Expression<Func<Quot, bool>> ToExpression()
    {
        return quot => quot.Author.Length > 0 && quot.Text.Length > 0;
    }
}

// Спецификация для отбора цитат
public class QuotSearchSpecification : Specification<Quot>
{
    public override Expression<Func<Quot, bool>> ToExpression()
    {
        return quot => true;
    }
}

// Применение спецификации для валидации цитат
public class GetQuotValidator
{
    public bool IsValid(Quot value)
    {
        QuotSearchSpecification specification = new();

        bool isOk = specification.IsSatisfiedBy(value);

        return isOk;
    }
}

public sealed class Quot
{
    public int Id {get; set;}
    public string Author { get; set; }
    public string Text { get; set; }
    public bool Removed { get; set; }
}

Tags:
Hubs:
Total votes 18: ↑12 and ↓6+6
Comments20

Articles