Паттерны проектирования в C# для продвинутых: от теории к коду

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Паттерны проектирования в C# для продвинутых: от теории к коду

Паттерны проектирования — это не просто абстрактные концепции из учебников. Для опытного C#-разработчика они становятся инструментарием, позволяющим создавать гибкую, расширяемую и поддерживаемую архитектуру приложений. В этой статье мы разберём три мощных паттерна, которые часто остаются за рамками базовых курсов: Visitor, Strategy и Command. Вы увидите не только их реализацию на C#, но и поймёте, как они решают реальные задачи в .NET-экосистеме.

1. Паттерн Visitor (Посетитель) — работа с иерархией объектов

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

Проблема

Представьте, что у вас есть система документооборота с классами TextDocument, Spreadsheet и Presentation. Вам нужно добавить функциональность экспорта в PDF, HTML и XML. Без паттерна Visitor вам пришлось бы модифицировать каждый класс, что нарушает принцип открытости/закрытости (OCP).

Реализация на C#

// 1. Интерфейс элемента
public interface IElement
{
    void Accept(IVisitor visitor);
}

// 2. Конкретные элементы
public class TextDocument : IElement
{
    public string Content { get; set; }
    public void Accept(IVisitor visitor) => visitor.Visit(this);
}

public class Spreadsheet : IElement
{
    public List<List<string>> Rows { get; set; }
    public void Accept(IVisitor visitor) => visitor.Visit(this);
}

// 3. Интерфейс посетителя
public interface IVisitor
{
    void Visit(TextDocument doc);
    void Visit(Spreadsheet sheet);
}

// 4. Конкретный посетитель — экспорт в HTML
public class HtmlExportVisitor : IVisitor
{
    public void Visit(TextDocument doc)
    {
        Console.WriteLine($"<h1>{doc.Content}</h1>");
    }

    public void Visit(Spreadsheet sheet)
    {
        Console.WriteLine("<table>");
        foreach (var row in sheet.Rows)
        {
            Console.WriteLine("<tr><td>" + string.Join("</td><td>", row) + "</td></tr>");
        }
        Console.WriteLine("</table>");
    }
}

// Использование
var docs = new List<IElement> { new TextDocument { Content = "Привет" }, new Spreadsheet { Rows = new List<List<string>> { new List<string> { "A", "B" } } } };
var visitor = new HtmlExportVisitor();
foreach (var doc in docs) doc.Accept(visitor);

Плюсы: легко добавлять новые операции, не трогая существующие классы. Минусы: если иерархия элементов часто меняется, Visitor становится неудобным.

2. Паттерн Strategy (Стратегия) — динамическая смена алгоритмов

Паттерн Strategy позволяет определять семейство алгоритмов, инкапсулировать каждый из них и делать их взаимозаменяемыми. Это классический способ избежать громадных if-else или switch конструкций.

Проблема

Вам нужно реализовать систему расчёта стоимости доставки. Способы доставки разные: курьером, почтой, самовывоз. Каждый способ имеет свою логику расчёта цены.

Реализация на C#

// 1. Интерфейс стратегии
public interface IShippingStrategy
{
    decimal CalculateCost(Order order);
}

// 2. Конкретные стратегии
public class CourierShipping : IShippingStrategy
{
    public decimal CalculateCost(Order order) => order.Weight * 5.0m + 100;
}

public class PostShipping : IShippingStrategy
{
    public decimal CalculateCost(Order order) => order.Weight * 2.0m + 50;
}

public class PickupShipping : IShippingStrategy
{
    public decimal CalculateCost(Order order) => 0; // Бесплатно
}

// 3. Контекст
public class Order
{
    public decimal Weight { get; set; }
    private IShippingStrategy _strategy;

    public void SetShippingStrategy(IShippingStrategy strategy) => _strategy = strategy;

    public decimal GetShippingCost() => _strategy?.CalculateCost(this) ?? throw new InvalidOperationException("Стратегия не задана");
}

// Использование
var order = new Order { Weight = 10 };
order.SetShippingStrategy(new CourierShipping());
Console.WriteLine($"Стоимость доставки курьером: {order.GetShippingCost()} руб.");

Плюсы: легко добавлять новые алгоритмы, код становится чище. Минусы: клиент должен знать о существующих стратегиях.

3. Паттерн Command (Команда) — инкапсуляция запроса

Паттерн Command превращает запросы в объекты. Это позволяет параметризовать методы, откладывать выполнение запросов, поддерживать отмену операций (undo) и вести журнал действий.

Проблема

Создайте систему для текстового редактора, которая поддерживает операции "вставить текст", "удалить символ" и "отменить последнее действие".

Реализация на C#

// 1. Интерфейс команды
public interface ICommand
{
    void Execute();
    void Undo();
}

// 2. Получатель (Receiver) — текстовый редактор
public class TextEditor
{
    private string _text = "";
    
    public void Insert(string text, int position)
    {
        _text = _text.Insert(position, text);
    }

    public void Delete(int position, int length)
    {
        _text = _text.Remove(position, length);
    }

    public string GetText() => _text;
}

// 3. Конкретная команда — вставка
public class InsertCommand : ICommand
{
    private readonly TextEditor _editor;
    private readonly string _text;
    private readonly int _position;

    public InsertCommand(TextEditor editor, string text, int position)
    {
        _editor = editor;
        _text = text;
        _position = position;
    }

    public void Execute() => _editor.Insert(_text, _position);

    public void Undo() => _editor.Delete(_position, _text.Length);
}

// 4. Invoker (вызывающий) — управляет историей
public class CommandHistory
{
    private readonly Stack<ICommand> _history = new Stack<ICommand>();

    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _history.Push(command                

Блоги

Рекомендаций по книгам