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

  1. Одноэлементный шаблон

Шаблон Singleton — один из самых популярных шаблонов в C#. Он используется, когда нам нужно убедиться, что в системе существует только один экземпляр класса. Этот шаблон полезен при создании объектов, поддерживающих единое состояние во всей системе. Хорошим примером этого является объект регистратора, который должен быть создан только один раз, и все экземпляры должны иметь одно и то же состояние.

Вот пример шаблона Singleton в C#.

public class Logger
{
    private static Logger instance;

    // private constructor to prevent object creation from outside the class
    private Logger() { }

    public static Logger Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new Logger();
            }
            return instance;
        }
    }

    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

В приведенном выше примере класс Logger реализует шаблон Singleton. У класса есть закрытый конструктор для предотвращения создания объектов вне класса. Класс также имеет частную статическую переменную экземпляра, которая содержит единственный экземпляр класса Logger. Общедоступное статическое свойство Instance используется для доступа к одному экземпляру класса Logger. Метод get свойства Instance проверяет, является ли переменная экземпляра нулевой, и создает новый экземпляр, если она равна нулю. Наконец, метод Log используется для записи сообщений в консоль.

Реализуя шаблон Singleton, мы гарантируем, что в системе существует только один экземпляр класса Logger, и все экземпляры имеют одно и то же состояние. Это полезно, когда мы хотим регистрировать сообщения по всей системе и поддерживать единый файл журнала.

Вот пример шаблона Singleton, реализованного в модели User на C#:

public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }

    private static User instance;

    // private constructor to prevent object creation from outside the class
    private User() { }

    public static User Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new User();
            }
            return instance;
        }
    }
}

В приведенном выше примере класс User реализует шаблон Singleton. У класса есть частные установщики для свойств Name, Email и Age, чтобы гарантировать, что эти значения могут быть установлены только один раз во время создания объекта. Класс также имеет частную статическую переменную экземпляра, которая содержит единственный экземпляр класса User. Общедоступное статическое свойство Instance используется для доступа к одному экземпляру класса User. Метод get свойства Instance проверяет, является ли переменная экземпляра нулевой, и создает новый экземпляр, если она равна нулю.

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

2. Заводской шаблон

Шаблон Factory — еще один популярный шаблон C#. Он предоставляет способ создавать объекты, не раскрывая логику создания клиенту. Шаблон Factory полезен, когда нам нужно создать объекты со сложной логикой инициализации или когда мы хотим создать объекты разных типов на основе определенного условия.

Вот пример шаблона Factory, реализованного в классе Logger на C#:

public interface ILogger
{
    void Log(string message);
}

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

public class FileLogger : ILogger
{
    private readonly string fileName;

    public FileLogger(string fileName)
    {
        this.fileName = fileName;
    }

    public void Log(string message)
    {
        using (var writer = new StreamWriter(fileName, true))
        {
            writer.WriteLine(message);
        }
    }
}

public static class LoggerFactory
{
    public static ILogger GetLogger(string loggerType)
    {
        switch (loggerType)
        {
            case "console":
                return new ConsoleLogger();
            case "file":
                return new FileLogger("log.txt");
            default:
                throw new ArgumentException("Invalid logger type.");
        }
    }
}

В приведенном выше примере мы определили интерфейс с именем ILogger, который определяет метод Log. Мы также создали два конкретных класса, реализующих интерфейс ILogger: ConsoleLogger и FileLogger. Класс ConsoleLogger записывает сообщения в консоль, а класс FileLogger записывает сообщения в файл.

Мы также создали статический класс с именем LoggerFactory, который содержит метод GetLogger. Метод GetLogger принимает строковый аргумент, указывающий тип создаваемого регистратора. Затем метод использует оператор switch для создания и возврата экземпляра соответствующего типа регистратора.

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

Вот пример шаблона Factory, реализованного в классе User на C#:

public interface IUser
{
    string Name { get; }
    string Email { get; }
    int Age { get; }
}

public class RegularUser : IUser
{
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}

public class PremiumUser : IUser
{
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
    public decimal Discount { get; set; }
}

public static class UserFactory
{
    public static IUser CreateUser(bool isPremium)
    {
        if (isPremium)
        {
            return new PremiumUser { Discount = 0.1m };
        }
        else
        {
            return new RegularUser();
        }
    }
}

В приведенном выше примере мы определили интерфейс с именем IUser, который определяет свойства Name, Email и Age. Мы также создали два конкретных класса, реализующих интерфейс IUser: RegularUser и PremiumUser. Класс PremiumUser имеет дополнительное свойство Discount.

Мы также создали статический класс с именем UserFactory, который содержит метод CreateUser. Метод CreateUser принимает логический аргумент, указывающий, следует ли создавать RegularUser или PremiumUser. Затем метод использует оператор if для создания и возврата экземпляра соответствующего типа пользователя.

Используя шаблон Factory, мы можем инкапсулировать логику создания пользователя в отдельный класс и отделить клиентский код от деталей реализации пользовательских классов. Клиентскому коду нужно только знать, создавать ли пользователя RegularUser или PremiumUser, а Factory позаботится о создании соответствующего экземпляра пользователя.

Вот еще один пример шаблона Factory, реализованного в классе Payment Gateway на C#:

public interface IPaymentGateway
{
    bool ProcessPayment(string cardNumber, decimal amount);
}

public class PayPalGateway : IPaymentGateway
{
    public bool ProcessPayment(string cardNumber, decimal amount)
    {
        // Process payment via PayPal API
        return true;
    }
}

public class StripeGateway : IPaymentGateway
{
    public bool ProcessPayment(string cardNumber, decimal amount)
    {
        // Process payment via Stripe API
        return true;
    }
}

public static class PaymentGatewayFactory
{
    public static IPaymentGateway GetGateway(string gatewayType)
    {
        switch (gatewayType)
        {
            case "paypal":
                return new PayPalGateway();
            case "stripe":
                return new StripeGateway();
            default:
                throw new ArgumentException("Invalid gateway type.");
        }
    }
}

В приведенном выше примере мы определили интерфейс с именем IPaymentGateway, который определяет метод ProcessPayment. Мы также создали два конкретных класса, которые реализуют интерфейс IPaymentGateway: PayPalGateway и StripeGateway. Метод ProcessPayment в каждом из этих классов реализует логику обработки платежей с использованием соответствующих API-интерфейсов платежного шлюза.

Мы также создали статический класс с именем PaymentGatewayFactory, который содержит метод GetGateway. Метод GetGateway принимает строковый аргумент, указывающий тип используемого платежного шлюза. Затем метод использует оператор switch для создания и возврата экземпляра соответствующего типа шлюза.

Используя шаблон Factory, мы можем отделить клиентский код от деталей реализации классов платежного шлюза. Клиентскому коду нужно только знать тип используемого платежного шлюза, а Фабрика позаботится о создании соответствующего экземпляра шлюза.

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

3. Шаблон наблюдателя

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

Вот пример шаблона Observer, реализованного в классе WeatherStation на C#:

using System.Collections.Generic;

public interface IObserver
{
    void Update(float temperature, float humidity, float pressure);
}

public interface ISubject
{
    void RegisterObserver(IObserver observer);
    void RemoveObserver(IObserver observer);
    void NotifyObservers();
}

public class WeatherStation : ISubject
{
    private float temperature;
    private float humidity;
    private float pressure;
    private List<IObserver> observers;

    public WeatherStation()
    {
        observers = new List<IObserver>();
    }

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

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

    public void NotifyObservers()
    {
        foreach (IObserver observer in observers)
        {
            observer.Update(temperature, humidity, pressure);
        }
    }

    public void SetMeasurements(float temperature, float humidity, float pressure)
    {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        NotifyObservers();
    }
}

public class CurrentConditionsDisplay : IObserver
{
    private float temperature;
    private float humidity;

    public void Update(float temperature, float humidity, float pressure)
    {
        this.temperature = temperature;
        this.humidity = humidity;
        Display();
    }

    public void Display()
    {
        Console.WriteLine("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
    }
}

В приведенном выше примере мы определили интерфейс с именем IObserver, который определяет метод Update. Мы также определили интерфейс с именем ISubject, который определяет методы RegisterObserver, RemoveObserver и NotifyObservers.

Мы создали класс WeatherStation, реализующий интерфейс ISubject. Класс WeatherStation поддерживает текущие показания температуры, влажности и давления, а также предоставляет методы для регистрации и удаления наблюдателей и уведомления наблюдателей об изменениях.

Мы также создали класс CurrentConditionsDisplay, реализующий интерфейс IObserver. Класс CurrentConditionsDisplay поддерживает собственные копии показаний температуры и влажности и предоставляет метод Display, который вызывается всякий раз, когда WeatherStation вызывает метод Update.

Используя шаблон Observer, мы можем отделить объект WeatherStation от объекта CurrentConditionsDisplay. Объект WeatherStation должен знать только, как регистрировать, удалять и уведомлять наблюдателей, а объект CurrentConditionsDisplay должен знать только, как обновлять и отображать свои собственные показания.

Таким образом, шаблон Observer способствует слабой связи между объектами, делая код более удобным для сопровождения и расширяемым.

Другой пример шаблона Observer можно реализовать в классе StockMarket на C#:

using System.Collections.Generic;

public interface IObserver
{
    void Update(string stockSymbol, decimal price);
}

public interface ISubject
{
    void RegisterObserver(IObserver observer);
    void RemoveObserver(IObserver observer);
    void NotifyObservers(string stockSymbol, decimal price);
}

public class StockMarket : ISubject
{
    private Dictionary<string, decimal> stockPrices;
    private List<IObserver> observers;

    public StockMarket()
    {
        stockPrices = new Dictionary<string, decimal>();
        observers = new List<IObserver>();
    }

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

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

    public void NotifyObservers(string stockSymbol, decimal price)
    {
        foreach (IObserver observer in observers)
        {
            observer.Update(stockSymbol, price);
        }
    }

    public void UpdateStockPrice(string stockSymbol, decimal price)
    {
        stockPrices[stockSymbol] = price;
        NotifyObservers(stockSymbol, price);
    }
}

public class StockTicker : IObserver
{
    private string stockSymbol;
    private decimal price;

    public StockTicker(string stockSymbol)
    {
        this.stockSymbol = stockSymbol;
    }

    public void Update(string stockSymbol, decimal price)
    {
        if (this.stockSymbol == stockSymbol)
        {
            this.price = price;
            Display();
        }
    }

    public void Display()
    {
        Console.WriteLine("Stock " + stockSymbol + " price: " + price);
    }
}

В приведенном выше примере мы определили интерфейс с именем IObserver, который определяет метод Update. Мы также определили интерфейс с именем ISubject, который определяет методы RegisterObserver, RemoveObserver и NotifyObservers.

Мы создали класс StockMarket, реализующий интерфейс ISubject. Класс StockMarket поддерживает словарь текущих цен на акции и предоставляет методы для регистрации и удаления наблюдателей и уведомления наблюдателей об изменениях.

Мы также создали класс StockTicker, реализующий интерфейс IObserver. Класс StockTicker поддерживает собственную копию символа и цены акции и предоставляет метод Display, который вызывается всякий раз, когда StockMarket вызывает метод Update.

Используя шаблон Observer, мы можем отделить объект StockMarket от объекта StockTicker. Объекту StockMarket нужно только знать, как регистрировать, удалять и уведомлять наблюдателей, а объекту StockTicker нужно только знать, как обновлять и отображать свой собственный символ акции и цену.

Таким образом, шаблон Observer способствует слабой связи между объектами, делая код более удобным для сопровождения и расширяемым.

4. Шаблон декоратора

Шаблон Decorator используется, когда мы хотим добавить функциональность объекту во время выполнения, не затрагивая другие объекты того же класса. Этот шаблон полезен, когда у нас есть класс с некоторыми фиксированными функциями, но мы хотим добавить к нему дополнительные функции, не изменяя его исходный код.

Вот пример шаблона Decorator в C#:

using System;

// The component interface defines operations that can be altered by decorators.
public interface IComponent
{
    void Operation();
}

// The concrete component provides the default implementation of the operations.
public class ConcreteComponent : IComponent
{
    public void Operation()
    {
        Console.WriteLine("ConcreteComponent operation");
    }
}

// The base decorator class follows the same interface as the component.
public abstract class BaseDecorator : IComponent
{
    private IComponent component;

    public BaseDecorator(IComponent component)
    {
        this.component = component;
    }

    public virtual void Operation()
    {
        component.Operation();
    }
}

// The concrete decorators modify the behavior of the component in different ways.
public class ConcreteDecoratorA : BaseDecorator
{
    public ConcreteDecoratorA(IComponent component) : base(component)
    {
    }

    public override void Operation()
    {
        base.Operation();
        Console.WriteLine("ConcreteDecoratorA operation");
    }
}

public class ConcreteDecoratorB : BaseDecorator
{
    public ConcreteDecoratorB(IComponent component) : base(component)
    {
    }

    public override void Operation()
    {
        base.Operation();
        Console.WriteLine("ConcreteDecoratorB operation");
    }
}

// Client code can use any combination of components and decorators.
public class Client
{
    public void Main()
    {
        // Create a concrete component and add decorators to it.
        ConcreteComponent component = new ConcreteComponent();
        ConcreteDecoratorA decoratorA = new ConcreteDecoratorA(component);
        ConcreteDecoratorB decoratorB = new ConcreteDecoratorB(decoratorA);

        // Execute the operation with all the decorators applied.
        decoratorB.Operation();
    }
}

В этом примере мы определили интерфейс с именем IComponent, который определяет метод Operation. Мы также создали класс ConcreteComponent, который реализует интерфейс IComponent и предоставляет реализацию метода Operation по умолчанию.

Затем мы создали абстрактный класс BaseDecorator, который реализует интерфейс IComponent и поддерживает ссылку на обернутый компонент. Мы также создали два конкретных декоратора, ConcreteDecoratorA и ConcreteDecoratorB, которые наследуются от BaseDecorator и изменяют поведение компонента, добавляя свои собственные операции до или после операции компонента по умолчанию.

Наконец, мы создали класс Client, который создает объект ConcreteComponent и украшает его с помощью ConcreteDecoratorA и ConcreteDecoratorB. Затем клиент выполняет метод Operation, который вызывает операцию по умолчанию объекта ConcreteComponent, за которой следуют операции, добавленные ConcreteDecoratorA и ConcreteDecoratorB.

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

Другой пример шаблона Decorator в C#:

using System;

// The component interface defines the basic operations of a document.
public interface IDocument
{
    void Open();
    void Close();
}

// The concrete component represents a plain text document.
public class TextDocument : IDocument
{
    private string content;

    public TextDocument(string content)
    {
        this.content = content;
    }

    public void Open()
    {
        Console.WriteLine("Opening text document...");
    }

    public void Close()
    {
        Console.WriteLine("Closing text document...");
    }

    public string Content
    {
        get { return content; }
        set { content = value; }
    }
}

// The base decorator class adds formatting to a document.
public abstract class DocumentDecorator : IDocument
{
    protected IDocument document;

    public DocumentDecorator(IDocument document)
    {
        this.document = document;
    }

    public virtual void Open()
    {
        document.Open();
    }

    public virtual void Close()
    {
        document.Close();
    }
}

// The concrete decorators add different types of formatting to the document.
public class BoldDocumentDecorator : DocumentDecorator
{
    public BoldDocumentDecorator(IDocument document) : base(document)
    {
    }

    public override void Open()
    {
        base.Open();
        Console.WriteLine("Adding bold formatting to the document...");
    }

    public override void Close()
    {
        base.Close();
        Console.WriteLine("Removing bold formatting from the document...");
    }
}

public class ItalicDocumentDecorator : DocumentDecorator
{
    public ItalicDocumentDecorator(IDocument document) : base(document)
    {
    }

    public override void Open()
    {
        base.Open();
        Console.WriteLine("Adding italic formatting to the document...");
    }

    public override void Close()
    {
        base.Close();
        Console.WriteLine("Removing italic formatting from the document...");
    }
}

// Client code can use any combination of components and decorators.
public class Client
{
    public void Main()
    {
        // Create a text document and add decorators to it.
        TextDocument document = new TextDocument("Hello, world!");
        BoldDocumentDecorator boldDecorator = new BoldDocumentDecorator(document);
        ItalicDocumentDecorator italicDecorator = new ItalicDocumentDecorator(boldDecorator);

        // Open and close the decorated document.
        italicDecorator.Open();
        Console.WriteLine(italicDecorator.Document.Content);
        italicDecorator.Close();
    }
}

В этом примере мы определили интерфейс с именем IDocument, который определяет методы Open и Close. Мы также создали класс TextDocument, реализующий интерфейс IDocument и представляющий обычный текстовый документ.

Затем мы создали базовый абстрактный класс DocumentDecorator, который реализует интерфейс IDocument и поддерживает ссылку на упакованный документ. Мы также создали два конкретных декоратора, BoldDocumentDecorator и ItalicDocumentDecorator, которые наследуются от DocumentDecorator и добавляют к документу полужирное и курсивное форматирование соответственно.

Наконец, мы создали класс Client, который создает объект TextDocument и украшает его с помощью BoldDocumentDecorator и ItalicDocumentDecorator. Затем клиент открывает и закрывает оформленный документ, который вызывает методы Open и Close декораторов и объект TextDocument.

Используя шаблон Decorator, мы можем добавлять новые функции к объектам во время выполнения, не изменяя их базовые классы. В этом примере мы добавили форматирование в документ без изменения класса TextDocument, что сделало код более гибким и простым в обслуживании.

5. Шаблон команды

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

Вот пример шаблона Command в C#:

using System;

// The Receiver class defines the object that will receive the commands.
public class Light
{
    public void TurnOn()
    {
        Console.WriteLine("Light turned on.");
    }

    public void TurnOff()
    {
        Console.WriteLine("Light turned off.");
    }
}

// The Command interface declares the execution method for the commands.
public interface ICommand
{
    void Execute();
}

// Concrete commands implement the Execute method and call the appropriate method on the receiver.
public class TurnOnLightCommand : ICommand
{
    private Light light;

    public TurnOnLightCommand(Light light)
    {
        this.light = light;
    }

    public void Execute()
    {
        light.TurnOn();
    }
}

public class TurnOffLightCommand : ICommand
{
    private Light light;

    public TurnOffLightCommand(Light light)
    {
        this.light = light;
    }

    public void Execute()
    {
        light.TurnOff();
    }
}

// The Invoker class holds the commands and calls their Execute methods.
public class Switch
{
    private ICommand turnOnCommand;
    private ICommand turnOffCommand;

    public Switch(ICommand turnOnCommand, ICommand turnOffCommand)
    {
        this.turnOnCommand = turnOnCommand;
        this.turnOffCommand = turnOffCommand;
    }

    public void TurnOn()
    {
        turnOnCommand.Execute();
    }

    public void TurnOff()
    {
        turnOffCommand.Execute();
    }
}

// The Client class creates the commands and the invoker and executes them.
public class Client
{
    public void Main()
    {
        Light light = new Light();
        ICommand turnOnCommand = new TurnOnLightCommand(light);
        ICommand turnOffCommand = new TurnOffLightCommand(light);
        Switch lightSwitch = new Switch(turnOnCommand, turnOffCommand);

        lightSwitch.TurnOn();
        lightSwitch.TurnOff();
    }
}

В этом примере мы определили класс Receiver с именем Light, который определяет объект, который будет получать команды. Мы также определили интерфейс Command, который объявляет метод выполнения для команд, и две конкретные команды, TurnOnLightCommand и TurnOffLightCommand, которые реализуют интерфейс ICommand и вызывают соответствующий метод для объекта Light.

Затем мы создали класс Invoker с именем Switch, который содержит команды и вызывает их методы Execute. Класс Switch имеет два метода TurnOn и TurnOff, которые выполняют соответствующие команды.

Наконец, мы создали класс Client, который создает команды и вызывающую программу и выполняет их. Клиент создает объект Light, создает объекты TurnOnLightCommand и TurnOffLightCommand с объектом Light в качестве параметра и создает объект Switch с командами в качестве параметров. Затем клиент выполняет команды, используя методы TurnOn и TurnOff объекта Switch.

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

Вот еще один пример шаблона Command в C#:

using System;

// The Receiver class defines the object that will receive the commands.
public class Car
{
    private int speed = 0;

    public void IncreaseSpeed(int amount)
    {
        speed += amount;
        Console.WriteLine($"Speed increased to {speed} km/h.");
    }

    public void DecreaseSpeed(int amount)
    {
        speed -= amount;
        Console.WriteLine($"Speed decreased to {speed} km/h.");
    }
}

// The Command interface declares the execution method for the commands.
public interface ICommand
{
    void Execute();
}

// Concrete commands implement the Execute method and call the appropriate method on the receiver.
public class IncreaseSpeedCommand : ICommand
{
    private Car car;
    private int amount;

    public IncreaseSpeedCommand(Car car, int amount)
    {
        this.car = car;
        this.amount = amount;
    }

    public void Execute()
    {
        car.IncreaseSpeed(amount);
    }
}

public class DecreaseSpeedCommand : ICommand
{
    private Car car;
    private int amount;

    public DecreaseSpeedCommand(Car car, int amount)
    {
        this.car = car;
        this.amount = amount;
    }

    public void Execute()
    {
        car.DecreaseSpeed(amount);
    }
}

// The Invoker class holds the commands and calls their Execute methods.
public class Driver
{
    private ICommand increaseSpeedCommand;
    private ICommand decreaseSpeedCommand;

    public Driver(ICommand increaseSpeedCommand, ICommand decreaseSpeedCommand)
    {
        this.increaseSpeedCommand = increaseSpeedCommand;
        this.decreaseSpeedCommand = decreaseSpeedCommand;
    }

    public void Accelerate()
    {
        increaseSpeedCommand.Execute();
    }

    public void Brake()
    {
        decreaseSpeedCommand.Execute();
    }
}

// The Client class creates the commands and the invoker and executes them.
public class Client
{
    public void Main()
    {
        Car car = new Car();
        ICommand increaseSpeedCommand = new IncreaseSpeedCommand(car, 10);
        ICommand decreaseSpeedCommand = new DecreaseSpeedCommand(car, 10);
        Driver driver = new Driver(increaseSpeedCommand, decreaseSpeedCommand);

        driver.Accelerate();
        driver.Brake();
    }
}

В этом примере мы определили класс Receiver с именем Car, который определяет объект, который будет получать команды. Мы также определили интерфейс Command, который объявляет метод выполнения для команд, и две конкретные команды, УвеличитьSpeedCommand и DecreaseSpeedCommand, которые реализуют интерфейс ICommand и вызывают соответствующий метод для объекта Car.

Затем мы создали класс Invoker с именем Driver, который содержит команды и вызывает их методы Execute. Класс Driver имеет два метода Accelerate и Brake, которые выполняют соответствующие команды.

Наконец, мы создали класс Client, который создает команды и вызывающую программу и выполняет их. Клиент создает объект «Автомобиль», создает объекты «Увеличить скорость команды» и «Уменьшить скорость команды» с объектом «Автомобиль» в качестве параметра и создает объект «Водитель» с командами в качестве параметров. Затем клиент выполняет команды, используя методы Accelerate и Brake объекта Driver.

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

6. Шаблон адаптера

Шаблон адаптера используется, когда мы хотим преобразовать интерфейс одного класса в интерфейс, ожидаемый другим классом. Этот шаблон полезен, когда у нас есть два класса, которые не могут взаимодействовать напрямую из-за несовместимых интерфейсов.

Вот пример шаблона адаптера в C#:

using System;

// The Target interface defines the operations that the client can perform.
public interface ITarget
{
    void Request();
}

// The Adaptee is the existing interface that needs to be adapted to the Target interface.
public class Adaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("Adaptee: SpecificRequest called.");
    }
}

// The Adapter implements the Target interface and adapts the Adaptee to it.
public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public void Request()
    {
        _adaptee.SpecificRequest();
    }
}

// The Client interacts with the Target interface.
public class Client
{
    public void Main()
    {
        Adaptee adaptee = new Adaptee();
        ITarget target = new Adapter(adaptee);

        target.Request();
    }
}

В этом примере мы определили класс Adaptee, который имеет метод с именем SpecificRequest. Мы хотим адаптировать этот интерфейс к целевому интерфейсу, который имеет метод с именем Request. Для этого мы определили класс Adapter, который реализует интерфейс Target и адаптирует к нему Adaptee.

Конструктор адаптера принимает объект Adaptee в качестве параметра, а метод Request класса адаптера вызывает метод SpecificRequest объекта Adaptee. Таким образом, мы можем использовать объект Adaptee через объект Adapter, как если бы он был объектом Target.

Мы также определили класс Client, который создает объект Adaptee, создает объект Adapter с объектом Adaptee в качестве параметра и использует интерфейс Target для вызова метода Request объекта Adapter.

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

Вот еще один пример шаблона адаптера в C#:

using System;

// The Target interface defines the operations that the client can perform.
public interface ITarget
{
    void Send(string message);
}

// The Adaptee is the existing interface that needs to be adapted to the Target interface.
public class LegacyMessenger
{
    public void SendMessage(string message)
    {
        Console.WriteLine($"LegacyMessenger: Sending message '{message}'");
    }
}

// The Adapter implements the Target interface and adapts the Adaptee to it.
public class MessengerAdapter : ITarget
{
    private readonly LegacyMessenger _legacyMessenger;

    public MessengerAdapter(LegacyMessenger legacyMessenger)
    {
        _legacyMessenger = legacyMessenger;
    }

    public void Send(string message)
    {
        _legacyMessenger.SendMessage(message);
    }
}

// The Client interacts with the Target interface.
public class Client
{
    public void Main()
    {
        LegacyMessenger legacyMessenger = new LegacyMessenger();
        ITarget target = new MessengerAdapter(legacyMessenger);

        target.Send("Hello, world!");
    }
}

В этом примере у нас есть класс LegacyMessenger с методом SendMessage, и мы хотим адаптировать этот интерфейс к интерфейсу Target с методом Send. Мы определили класс MessengerAdapter, который реализует интерфейс Target и адаптирует к нему LegacyMessenger.

Конструктор MessengerAdapter принимает объект LegacyMessenger в качестве параметра, а метод Send класса MessengerAdapter вызывает метод SendMessage объекта LegacyMessenger. Таким образом, мы можем использовать объект LegacyMessenger через объект MessengerAdapter, как если бы он был объектом Target.

Мы также определили класс Client, который создает объект LegacyMessenger, создает объект MessengerAdapter с объектом LegacyMessenger в качестве параметра и использует интерфейс Target для вызова метода Send объекта MessengerAdapter.

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

7. Шаблон стратегии

Шаблон стратегии используется, когда мы хотим инкапсулировать различные алгоритмы и позволить клиенту выбирать, какой алгоритм использовать во время выполнения. Этот шаблон полезен, когда у нас есть несколько алгоритмов, которые можно использовать для решения проблемы, и мы хотим выбрать подходящий алгоритм на основе конкретных условий.

Вот пример шаблона Strategy в C#:

using System;

// The Strategy interface defines the operations that each strategy must implement.
public interface IStrategy
{
    void Execute(int[] data);
}

// The ConcreteStrategyA class implements a strategy that sorts an array in ascending order.
public class ConcreteStrategyA : IStrategy
{
    public void Execute(int[] data)
    {
        Array.Sort(data);
        Console.WriteLine("Sorted data in ascending order:");
        foreach (int item in data)
        {
            Console.Write($"{item} ");
        }
        Console.WriteLine();
    }
}

// The ConcreteStrategyB class implements a strategy that sorts an array in descending order.
public class ConcreteStrategyB : IStrategy
{
    public void Execute(int[] data)
    {
        Array.Sort(data);
        Array.Reverse(data);
        Console.WriteLine("Sorted data in descending order:");
        foreach (int item in data)
        {
            Console.Write($"{item} ");
        }
        Console.WriteLine();
    }
}

// The Context class maintains a reference to a Strategy object and provides an interface to interact with it.
public class Context
{
    private readonly IStrategy _strategy;

    public Context(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void SetStrategy(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void SortData(int[] data)
    {
        _strategy.Execute(data);
    }
}

// The Client interacts with the Context class to use a specific strategy.
public class Client
{
    public void Main()
    {
        int[] data = { 4, 2, 1, 5, 3 };

        Context context = new Context(new ConcreteStrategyA());
        context.SortData(data);

        context.SetStrategy(new ConcreteStrategyB());
        context.SortData(data);
    }
}

В этом примере мы определили две стратегии: ConcreteStrategyA и ConcreteStrategyB. ConcreteStrategyA сортирует массив в порядке возрастания, а ConcreteStrategyB сортирует массив в порядке убывания. Обе стратегии реализуют интерфейс IStrategy.

Мы также определили класс Context, который поддерживает ссылку на объект Strategy и предоставляет интерфейс для взаимодействия с ним. Класс Context имеет метод SortData, который вызывает метод Execute текущего объекта стратегии.

Мы определили класс Client, который создает массив целых чисел, создает объект Context с объектом ConcreteStrategyA в качестве параметра и вызывает метод SortData объекта Context. Затем мы устанавливаем стратегию объекта Context на объект ConcreteStrategyB и снова вызываем метод SortData.

Используя паттерн Стратегия, мы можем инкапсулировать различные алгоритмы и сделать их взаимозаменяемыми без изменения кода, который их использует. Это позволяет нам повторно использовать существующий код и делает код более гибким и простым в обслуживании.

Вот еще один пример шаблона Strategy в C#:

using System;

// The Strategy interface defines the operations that each strategy must implement.
public interface IShippingStrategy
{
    decimal CalculateShippingCost(decimal weight, decimal distance);
}

// The ConcreteStrategyA class implements a strategy for ground shipping.
public class GroundShippingStrategy : IShippingStrategy
{
    public decimal CalculateShippingCost(decimal weight, decimal distance)
    {
        decimal cost = 0;

        // Calculate shipping cost based on weight and distance for ground shipping
        // ...

        return cost;
    }
}

// The ConcreteStrategyB class implements a strategy for air shipping.
public class AirShippingStrategy : IShippingStrategy
{
    public decimal CalculateShippingCost(decimal weight, decimal distance)
    {
        decimal cost = 0;

        // Calculate shipping cost based on weight and distance for air shipping
        // ...

        return cost;
    }
}

// The Context class maintains a reference to a ShippingStrategy object and provides an interface to interact with it.
public class ShippingContext
{
    private readonly IShippingStrategy _shippingStrategy;

    public ShippingContext(IShippingStrategy shippingStrategy)
    {
        _shippingStrategy = shippingStrategy;
    }

    public void SetShippingStrategy(IShippingStrategy shippingStrategy)
    {
        _shippingStrategy = shippingStrategy;
    }

    public decimal CalculateShippingCost(decimal weight, decimal distance)
    {
        return _shippingStrategy.CalculateShippingCost(weight, distance);
    }
}

// The Client interacts with the ShippingContext class to calculate the shipping cost using a specific strategy.
public class Client
{
    public void Main()
    {
        decimal weight = 10;
        decimal distance = 100;

        ShippingContext context = new ShippingContext(new GroundShippingStrategy());
        decimal groundShippingCost = context.CalculateShippingCost(weight, distance);

        context.SetShippingStrategy(new AirShippingStrategy());
        decimal airShippingCost = context.CalculateShippingCost(weight, distance);

        Console.WriteLine($"Ground shipping cost: {groundShippingCost}");
        Console.WriteLine($"Air shipping cost: {airShippingCost}");
    }
}

В этом примере мы определили две стратегии: GroundShippingStrategy и AirShippingStrategy. GroundShippingStrategy рассчитывает стоимость наземной доставки на основе веса и расстояния, а AirShippingStrategy рассчитывает стоимость доставки по воздуху на основе веса и расстояния. Обе стратегии реализуют интерфейс IShippingStrategy.

Мы также определили класс ShippingContext, который поддерживает ссылку на объект ShippingStrategy и предоставляет интерфейс для взаимодействия с ним. Класс ShippingContext имеет метод CalculateShippingCost, который вызывает метод CalculateShippingCost текущего объекта стратегии.

Мы определили класс Client, который создает две десятичные переменные, создает объект ShippingContext с объектом GroundShippingStrategy в качестве параметра и вызывает метод CalculateShippingCost объекта ShippingContext с двумя десятичными переменными в качестве параметров. Затем мы устанавливаем стратегию объекта ShippingContext в объект AirShippingStrategy и снова вызываем метод CalculateShippingCost с теми же десятичными переменными.

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

8. Шаблон метода шаблона

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

Вот пример шаблона Template Method в C#:

using System;

// The AbstractClass defines the template method that contains a series of method calls that define the algorithm.
public abstract class AbstractClass
{
    public void TemplateMethod()
    {
        Operation1();
        Operation2();
        Operation3();
    }

    // The primitive operations are abstract methods that must be implemented by the subclasses.
    protected abstract void Operation1();
    protected abstract void Operation2();

    // The hook operation is an optional method that can be overridden by the subclasses.
    protected virtual void Operation3()
    {
        Console.WriteLine("AbstractClass.Operation3() called");
    }
}

// The ConcreteClassA implements the primitive operations to carry out the algorithm defined in the AbstractClass.
public class ConcreteClassA : AbstractClass
{
    protected override void Operation1()
    {
        Console.WriteLine("ConcreteClassA.Operation1() called");
    }

    protected override void Operation2()
    {
        Console.WriteLine("ConcreteClassA.Operation2() called");
    }
}

// The ConcreteClassB implements the primitive operations to carry out a different algorithm defined in the AbstractClass.
public class ConcreteClassB : AbstractClass
{
    protected override void Operation1()
    {
        Console.WriteLine("ConcreteClassB.Operation1() called");
    }

    protected override void Operation2()
    {
        Console.WriteLine("ConcreteClassB.Operation2() called");
    }

    protected override void Operation3()
    {
        Console.WriteLine("ConcreteClassB.Operation3() called");
    }
}

// The Client uses the TemplateMethod to carry out the algorithm defined in the AbstractClass using a ConcreteClass object.
public class Client
{
    public void Main()
    {
        AbstractClass abstractClass = new ConcreteClassA();
        abstractClass.TemplateMethod();

        Console.WriteLine();

        abstractClass = new ConcreteClassB();
        abstractClass.TemplateMethod();
    }
}

В этом примере мы определили абстрактный класс, который определяет метод шаблона с именем TemplateMethod. TemplateMethod содержит серию вызовов методов, определяющих алгоритм. Мы также определили две примитивные операции, Operation1 и Operation2, которые должны быть реализованы подклассами, и операцию-ловушку Operation3, которая является необязательным методом, который может быть переопределен подклассами.

Мы определили два конкретных класса, ConcreteClassA и ConcreteClassB, которые реализуют примитивные операции для выполнения различных алгоритмов, определенных в AbstractClass.

Мы также определили класс Client, который создает объект AbstractClass с использованием объекта ConcreteClassA и вызывает TemplateMethod для выполнения алгоритма, определенного в AbstractClass. Затем мы создаем объект AbstractClass, используя объект ConcreteClassB, и снова вызываем TemplateMethod для выполнения другого алгоритма, определенного в AbstractClass.

Используя шаблон Template Method, мы можем определить скелет алгоритма в базовом классе и позволить подклассам реализовывать детали алгоритма, реализуя примитивные операции. Это позволяет нам повторно использовать существующий код и делает код более гибким и простым в обслуживании.

Другой пример шаблона Template Method, примененного к пользовательской модели в C#:

using System;

// The AbstractUser class defines a template method called AuthenticateUser that contains a series of method calls that define the authentication process.
public abstract class AbstractUser
{
    public void AuthenticateUser(string username, string password)
    {
        if (IsValidUsername(username))
        {
            if (IsValidPassword(password))
            {
                Console.WriteLine($"User {username} authenticated successfully.");
            }
            else
            {
                Console.WriteLine($"Invalid password for user {username}.");
            }
        }
        else
        {
            Console.WriteLine($"Invalid username: {username}.");
        }
    }

    // The IsValidUsername and IsValidPassword methods are abstract methods that must be implemented by the subclasses.
    protected abstract bool IsValidUsername(string username);
    protected abstract bool IsValidPassword(string password);
}

// The BasicUser class implements the IsValidUsername and IsValidPassword methods to define the authentication process for basic users.
public class BasicUser : AbstractUser
{
    protected override bool IsValidUsername(string username)
    {
        // Basic users have usernames that are between 5 and 10 characters long.
        return username.Length >= 5 && username.Length <= 10;
    }

    protected override bool IsValidPassword(string password)
    {
        // Basic users have passwords that are at least 8 characters long.
        return password.Length >= 8;
    }
}

// The AdminUser class implements the IsValidUsername and IsValidPassword methods to define the authentication process for admin users.
public class AdminUser : AbstractUser
{
    protected override bool IsValidUsername(string username)
    {
        // Admin users have usernames that are between 8 and 15 characters long and start with "admin_".
        return username.StartsWith("admin_") && username.Length >= 8 && username.Length <= 15;
    }

    protected override bool IsValidPassword(string password)
    {
        // Admin users have passwords that are at least 10 characters long and contain at least one digit and one uppercase letter.
        bool hasDigit = false;
        bool hasUppercase = false;

        foreach (char c in password)
        {
            if (Char.IsDigit(c))
            {
                hasDigit = true;
            }
            else if (Char.IsUpper(c))
            {
                hasUppercase = true;
            }

            if (hasDigit && hasUppercase)
            {
                return true;
            }
        }

        return false;
    }
}

// The Client uses the AuthenticateUser method to authenticate a user.
public class Client
{
    public void Main()
    {
        AbstractUser basicUser = new BasicUser();
        basicUser.AuthenticateUser("johndoe", "password123");

        Console.WriteLine();

        AbstractUser adminUser = new AdminUser();
        adminUser.AuthenticateUser("admin_jane", "Password123");
    }
}

В этом примере мы определили класс AbstractUser, который определяет шаблонный метод AuthenticateUser, содержащий ряд вызовов методов, определяющих процесс аутентификации. Мы также определили два абстрактных метода, IsValidUsername и IsValidPassword, которые должны быть реализованы подклассами для определения правил проверки имен пользователей и паролей.

Мы определили два конкретных класса, BasicUser и AdminUser, которые реализуют методы IsValidUsername и IsValidPassword для определения процесса аутентификации для обычных пользователей и пользователей-администраторов соответственно.

Мы также определили класс Client, который создает объект BasicUser и объект AdminUser и вызывает метод AuthenticateUser для аутентификации пользователей с использованием их имен пользователей и паролей. Процесс проверки подлинности определяется методом шаблона в классе AbstractUser, который вызывает методы IsValidUsername и IsValidPassword в конкретных классах для проверки ввода.

Используя шаблон Template Method, мы можем инкапсулировать общий процесс аутентификации в классе AbstractUser и позволить конкретным подклассам определять свои собственные правила проверки имен пользователей и паролей.

В этом примере класс BasicUser определяет правила проверки, требующие, чтобы имя пользователя имело длину от 5 до 10 символов, а пароль — не менее 8 символов. Класс AdminUser определяет правила проверки, требующие, чтобы имя пользователя начиналось с «admin_», имело длину от 8 до 15 символов, а пароль — не менее 10 символов и содержал как минимум одну цифру и одну заглавную букву.

Этот подход позволяет нам повторно использовать общий процесс аутентификации для нескольких типов пользователей, в то же время позволяя каждому типу пользователей иметь свои собственные правила проверки. Это также упрощает добавление новых типов пользователей в будущем путем простого создания нового конкретного подкласса, реализующего методы IsValidUsername и IsValidPassword.

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

9. Шаблон состояния

Шаблон состояния используется, когда мы хотим изменить поведение объекта на основе его состояния. Этот шаблон полезен, когда у нас есть объект, который может находиться в нескольких состояниях, и мы хотим определить поведение объекта на основе его состояния.

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

Одним из примеров шаблона State в C# является контроллер светофора. В этом примере мы создадим класс TrafficLight, который может находиться в одном из трех состояний: красном, желтом или зеленом. Каждое состояние будет представлено отдельным классом, реализующим интерфейс ITrafficLightState.

Вот как может выглядеть класс TrafficLight:

public class TrafficLight
{
    private ITrafficLightState currentState;

    public TrafficLight()
    {
        currentState = new RedState();
    }

    public void ChangeState(ITrafficLightState newState)
    {
        currentState = newState;
    }

    public void Request()
    {
        currentState.HandleRequest(this);
    }
}

Класс TrafficLight имеет поле currentState, в котором хранится текущее состояние светофора. У него есть конструктор, который инициализирует состояние красным, и два метода: ChangeState, который позволяет изменить состояние, и Request, который делегирует запрос в текущее состояние.

Далее мы создадим интерфейс ITrafficLightState, определяющий методы, которые должен реализовывать каждый класс состояния:

public interface ITrafficLightState
{
    void HandleRequest(TrafficLight light);
}

Каждый класс состояния будет реализовывать этот интерфейс и определять собственную реализацию метода HandleRequest.

Вот пример класса RedState:

public class RedState : ITrafficLightState
{
    public void HandleRequest(TrafficLight light)
    {
        Console.WriteLine("Traffic light is red");
        light.ChangeState(new GreenState());
    }
}

Класс RedState реализует метод HandleRequest, выводя сообщение о том, что сигнал светофора красный, а затем изменяя состояние на GreenState.

Вот пример класса GreenState:

public class GreenState : ITrafficLightState
{
    public void HandleRequest(TrafficLight light)
    {
        Console.WriteLine("Traffic light is green");
        light.ChangeState(new YellowState());
    }
}

Класс GreenState реализует метод HandleRequest, выводя информацию о том, что светофор зеленый, а затем изменяя состояние на YellowState.

Наконец, вот пример класса YellowState:

public class YellowState : ITrafficLightState
{
    public void HandleRequest(TrafficLight light)
    {
        Console.WriteLine("Traffic light is yellow");
        light.ChangeState(new RedState());
    }
}

Класс YellowState реализует метод HandleRequest, выводя сообщение о желтом сигнале светофора и затем изменяя состояние на RedState.

С помощью этой реализации мы можем создать объект TrafficLight и изменить его состояние, вызвав его метод ChangeState. Когда мы вызываем метод Request, он делегирует запрос текущему состоянию, которое будет обрабатывать его на основе его конкретного поведения. Это позволяет нам легко реализовать контроллер светофора, который меняет свое поведение в зависимости от своего внутреннего состояния.

Еще одним примером шаблона State в C# является видеопроигрыватель, который может находиться в одном из трех состояний: Playing, Paused или Stopped. Каждое состояние будет представлено отдельным классом, реализующим интерфейс IVideoPlayerState.

Вот как может выглядеть класс VideoPlayer:

public class VideoPlayer
{
    private IVideoPlayerState currentState;
    private string currentVideo;

    public VideoPlayer()
    {
        currentState = new StoppedState();
    }

    public void ChangeState(IVideoPlayerState newState)
    {
        currentState = newState;
    }

    public void Play(string video)
    {
        currentVideo = video;
        currentState.Play(this);
    }

    public void Pause()
    {
        currentState.Pause(this);
    }

    public void Stop()
    {
        currentState.Stop(this);
    }

    public string GetCurrentVideo()
    {
        return currentVideo;
    }
}

В классе VideoPlayer есть поле currentState, в котором хранится текущее состояние видеопроигрывателя. У него есть конструктор, который инициализирует состояние Stopped, и четыре метода: ChangeState, который позволяет изменить состояние, Play, который запускает воспроизведение видео, Pause, который приостанавливает воспроизводимое в данный момент видео, Stop, который останавливает воспроизводимое в данный момент видео. и GetCurrentVideo, который возвращает имя воспроизводимого видео.

Далее мы создадим интерфейс IVideoPlayerState, определяющий методы, которые должен реализовать каждый класс состояния:

public interface IVideoPlayerState
{
    void Play(VideoPlayer player);
    void Pause(VideoPlayer player);
    void Stop(VideoPlayer player);
}
//Each state class will implement this interface and define its own implementation of the Play, Pause, and Stop methods.

//Here's an example of the PlayingState class:
public class PlayingState : IVideoPlayerState
{
    public void Play(VideoPlayer player)
    {
        Console.WriteLine("Video is already playing");
    }

    public void Pause(VideoPlayer player)
    {
        Console.WriteLine("Pausing video");
        player.ChangeState(new PausedState());
    }

    public void Stop(VideoPlayer player)
    {
        Console.WriteLine("Stopping video");
        player.ChangeState(new StoppedState());
    }
}

Класс PlayingState реализует методы Play, Pause и Stop. Когда вызывается метод Play, он выводит, что видео уже воспроизводится. При вызове метода Pause он выводит сообщение о приостановке воспроизведения видео и изменяет состояние на PausedState. Когда вызывается метод Stop, он выводит сообщение об остановке видео и меняет состояние на StoppedState.

Вот пример класса PausedState:

public class PausedState : IVideoPlayerState
{
    public void Play(VideoPlayer player)
    {
        Console.WriteLine("Resuming video");
        player.ChangeState(new PlayingState());
    }

    public void Pause(VideoPlayer player)
    {
        Console.WriteLine("Video is already paused");
    }

    public void Stop(VideoPlayer player)
    {
        Console.WriteLine("Stopping video");
        player.ChangeState(new StoppedState());
    }
}

10. Шаблон фасада

Шаблон Facade используется, когда мы хотим предоставить упрощенный интерфейс для сложной системы. Этот шаблон полезен, когда у нас есть сложная система, состоящая из нескольких подсистем, и мы хотим предоставить клиенту простой интерфейс.

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

Возьмем в качестве примера автомобильный двигатель. Автомобильный двигатель состоит из множества сложных деталей, таких как поршень, коленчатый вал, цилиндр, система впрыска топлива и так далее. Чтобы запустить двигатель, вам необходимо выполнить сложный набор действий, таких как поворот ключа в замке зажигания, нажатие на педаль акселератора и так далее. Шаблон Facade можно использовать для упрощения процесса запуска механизма, предоставляя простой интерфейс, который абстрагирует детали базовой реализации.

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

// Complex system of classes
class Piston
{
    public void Move() { /* Move piston */ }
}

class Crankshaft
{
    public void Rotate() { /* Rotate crankshaft */ }
}

class FuelInjectionSystem
{
    public void InjectFuel() { /* Inject fuel */ }
}

// Facade class
class CarEngineFacade
{
    private readonly Piston piston;
    private readonly Crankshaft crankshaft;
    private readonly FuelInjectionSystem fuelInjectionSystem;

    public CarEngineFacade()
    {
        piston = new Piston();
        crankshaft = new Crankshaft();
        fuelInjectionSystem = new FuelInjectionSystem();
    }

    public void Start()
    {
        piston.Move();
        crankshaft.Rotate();
        fuelInjectionSystem.InjectFuel();
        Console.WriteLine("Engine started");
    }

    public void Stop()
    {
        Console.WriteLine("Stopping engine");
    }
}

В приведенном выше коде мы создали класс CarEngineFacade, который предоставляет метод Start для запуска двигателя и метод Stop для его остановки. Класс CarEngineFacade внутренне использует сложную систему классов, таких как Piston, Crankshaft и FuelInjectionSystem, для запуска и остановки двигателя.

Чтобы запустить двигатель с помощью класса CarEngineFacade, мы можем сделать:

CarEngineFacade engine = new CarEngineFacade();
engine.Start();

Это распечатает «Двигатель запущен» и внутренне запустит двигатель, перемещая поршень, вращая коленчатый вал и впрыскивая топливо с помощью системы впрыска топлива.

Чтобы остановить двигатель, мы можем сделать:

engine.Stop();

Это распечатает «Остановка двигателя».

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

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