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

Введение

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


Порождающие паттерны: контроль создания объектов

1.1. Singleton (Одиночка) — современные реализации

Классическая проблема: гарантировать, что у класса будет только один экземпляр, и предоставить к нему глобальную точку доступа.

1.1.1. Потокобезопасная реализация с Lazy<T>

public sealed class ConfigurationManager
{
    // Lazy<T> гарантирует потокобезопасную ленивую инициализацию
    private static readonly Lazy<ConfigurationManager> _instance = 
        new Lazy<ConfigurationManager>(() => new ConfigurationManager());
    
    public static ConfigurationManager Instance => _instance.Value;
    
    private readonly ConcurrentDictionary<string, string> _settings;
    
    // Приватный конструктор предотвращает создание экземпляров извне
    private ConfigurationManager()
    {
        _settings = new ConcurrentDictionary<string, string>();
        LoadConfiguration();
    }
    
    public string GetSetting(string key) => _settings.GetValueOrDefault(key);
    
    private void LoadConfiguration()
    {
        // Загрузка конфигурации из файла, базы данных или среды выполнения
        _settings["ApiEndpoint"] = Environment.GetEnvironmentVariable("API_ENDPOINT") 
                                 ?? "https://api.default.com";
        _settings["RetryCount"] = "3";
    }
}

// Использование
var config = ConfigurationManager.Instance;
var endpoint = config.GetSetting("ApiEndpoint");

1.1.2. Проблемы и ограничения синглтона

// АНТИПАТТЕРН: Синглтон как глобальное состояние
public class GlobalState // Плохо!
{
    public static GlobalState Instance { get; } = new GlobalState();
    public string CurrentUser { get; set; } // Общее изменяемое состояние!
}

// ПРАВИЛЬНЫЙ ПОДХОД: Внедрение зависимостей
public interface IConfigurationService
{
    string GetSetting(string key);
}

public class ConfigurationService : IConfigurationService
{
    // Регистрируем как Singleton в DI-контейнере
    private readonly IReadOnlyDictionary<string, string> _settings;
    
    public ConfigurationService(IConfiguration configuration)
    {
        _settings = configuration.GetSection("AppSettings")
            .Get<Dictionary<string, string>>()
            .AsReadOnly();
    }
    
    public string GetSetting(string key) => _settings.GetValueOrDefault(key);
}

// В ASP.NET Core Startup.cs
services.AddSingleton<IConfigurationService, ConfigurationService>();

1.2. Factory Method (Фабричный метод) и Abstract Factory (Абстрактная фабрика)

1.2.1. Фабричный метод с обобщениями

public abstract class Document
{
    public abstract string Content { get; }
    public abstract void Render();
}

public class PdfDocument : Document
{
    public override string Content => "PDF Content";
    public override void Render() => Console.WriteLine("Rendering PDF...");
}

public class WordDocument : Document
{
    public override string Content => "Word Content";
    public override void Render() => Console.WriteLine("Rendering Word...");
}

public abstract class DocumentCreator
{
    // Фабричный метод
    public abstract Document CreateDocument();
    
    // Бизнес-логика, использующая фабричный метод
    public void ProcessDocument()
    {
        var document = CreateDocument();
        Console.WriteLine($"Processing: {document.Content}");
        document.Render();
    }
}

public class PdfCreator : DocumentCreator
{
    public override Document CreateDocument() => new PdfDocument();
}

public class WordCreator : DocumentCreator
{
    public override Document CreateDocument() => new WordDocument();
}

// Использование
DocumentCreator creator = new PdfCreator();
creator.ProcessDocument();

1.2.2. Абстрактная фабрика для кроссплатформенных UI

public interface IButton
{
    void Render();
    void OnClick(Action action);
}

public interface ITextBox
{
    void Render();
    string GetText();
}

// Абстрактная фабрика
public interface IUIFactory
{
    IButton CreateButton();
    ITextBox CreateTextBox();
}

// Конкретные фабрики для разных платформ
public class WindowsUIFactory : IUIFactory
{
    public IButton CreateButton() => new WindowsButton();
    public ITextBox CreateTextBox() => new WindowsTextBox();
}

public class WebUIFactory : IUIFactory
{
    public IButton CreateButton() => new WebButton();
    public ITextBox CreateTextBox() => new WebTextBox();
}

public class Application
{
    private readonly IUIFactory _uiFactory;
    
    public Application(IUIFactory uiFactory)
    {
        _uiFactory = uiFactory;
    }
    
    public void RenderUI()
    {
        var button = _uiFactory.CreateButton();
        var textBox = _uiFactory.CreateTextBox();
        
        button.OnClick(() => Console.WriteLine($"Button clicked: {textBox.GetText()}"));
        button.Render();
        textBox.Render();
    }
}

// Использование с DI
var platform = Environment.OSVersion.Platform == PlatformID.Win32NT 
    ? new WindowsUIFactory() 
    : new WebUIFactory();

var app = new Application(platform);
app.RenderUI();

Структурные паттерны: организация классов и объектов

2.1. Adapter (Адаптер) — интеграция legacy-кода

2.1.1. Адаптер для сторонней библиотеки

// Старая система (legacy code)
public class LegacyLogger
{
    public void LogMessage(string severity, string message, DateTime timestamp)
    {
        // Сложный форматированный вывод
        Console.WriteLine($"[{timestamp:yyyy-MM-dd HH:mm:ss}] {severity}: {message}");
    }
}

// Новый интерфейс, принятый в проекте
public interface IModernLogger
{
    void LogInfo(string message);
    void LogError(string message);
    void LogWarning(string message);
}

// Адаптер
public class LegacyLoggerAdapter : IModernLogger
{
    private readonly LegacyLogger _legacyLogger;
    
    public LegacyLoggerAdapter(LegacyLogger legacyLogger)
    {
        _legacyLogger = legacyLogger;
    }
    
    public void LogInfo(string message)
    {
        _legacyLogger.LogMessage("INFO", message, DateTime.Now);
    }
    
    public void LogError(string message)
    {
        _legacyLogger.LogMessage("ERROR", message, DateTime.Now);
    }
    
    public void LogWarning(string message)
    {
        _legacyLogger.LogMessage("WARNING", message, DateTime.Now);
    }
}

// Использование в современном коде
public class OrderService
{
    private readonly IModernLogger _logger;
    
    public OrderService(IModernLogger logger)
    {
        _logger = logger;
    }
    
    public void ProcessOrder(Order order)
    {
        try
        {
            _logger.LogInfo($"Processing order {order.Id}");
            // Логика обработки
            _logger.LogInfo($"Order {order.Id} processed successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError($"Failed to process order {order.Id}: {ex.Message}");
        }
    }
}

2.2. Decorator (Декоратор) — добавление функциональности без изменения классов

2.2.1. Декоратор для кэширования

public interface IDataService
{
    Task<string> GetDataAsync(string key);
}

// Базовая реализация
public class DatabaseDataService : IDataService
{
    public async Task<string> GetDataAsync(string key)
    {
        await Task.Delay(100); // Имитация долгого запроса к БД
        return $"Data for {key} from database";
    }
}

// Декоратор для логирования
public class LoggingDataServiceDecorator : IDataService
{
    private readonly IDataService _innerService;
    private readonly ILogger _logger;
    
    public LoggingDataServiceDecorator(IDataService innerService, ILogger logger)
    {
        _innerService = innerService;
        _logger = logger;
    }
    
    public async Task<string> GetDataAsync(string key)
    {
        _logger.LogInformation($"Getting data for key: {key}");
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            var result = await _innerService.GetDataAsync(key);
            stopwatch.Stop();
            
            _logger.LogInformation($"Successfully got data for {key} in {stopwatch.ElapsedMilliseconds}ms");
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"Failed to get data for key: {key}");
            throw;
        }
    }
}

// Декоратор для кэширования
public class CachingDataServiceDecorator : IDataService
{
    private readonly IDataService _innerService;
    private readonly IMemoryCache _cache;
    private readonly TimeSpan _cacheDuration;
    
    public CachingDataServiceDecorator(
        IDataService innerService, 
        IMemoryCache cache, 
        TimeSpan cacheDuration)
    {
        _innerService = innerService;
        _cache = cache;
        _cacheDuration = cacheDuration;
    }
    
    public async Task<string> GetDataAsync(string key)
    {
        if (_cache.TryGetValue<string>(key, out var cachedData))
        {
            return cachedData;
        }
        
        var data = await _innerService.GetDataAsync(key);
        
        _cache.Set(key, data, _cacheDuration);
        
        return data;
    }
}

// Регистрация цепочки декораторов в DI
services.AddScoped<DatabaseDataService>();
services.Decorate<IDataService, LoggingDataServiceDecorator>();
services.Decorate<IDataService, CachingDataServiceDecorator>();

// Использование - клиент получает полностью обернутый сервис
public class ReportGenerator
{
    private readonly IDataService _dataService;
    
    public ReportGenerator(IDataService dataService)
    {
        // Внедряется цепочка: Caching -> Logging -> Database
        _dataService = dataService;
    }
    
    public async Task GenerateReportAsync()
    {
        var data = await _dataService.GetDataAsync("report_data");
        // data будет залогирована и закэширована автоматически
    }
}

2.3. Composite (Компоновщик) — древовидные структуры

2.3.1. Система управления файлами и папками

public abstract class FileSystemComponent
{
    public string Name { get; }
    public virtual long Size { get; }
    
    protected FileSystemComponent(string name)
    {
        Name = name;
    }
    
    public abstract void Display(int indent = 0);
}

// Листовой элемент
public class File : FileSystemComponent
{
    public long FileSize { get; }
    
    public override long Size => FileSize;
    
    public File(string name, long size) : base(name)
    {
        FileSize = size;
    }
    
    public override void Display(int indent = 0)
    {
        Console.WriteLine($"{new string(' ', indent)}📄 {Name} ({Size} bytes)");
    }
}

// Составной элемент
public class Directory : FileSystemComponent
{
    private readonly List<FileSystemComponent> _children = new();
    
    public override long Size => _children.Sum(c => c.Size);
    
    public Directory(string name) : base(name) { }
    
    public void Add(FileSystemComponent component)
    {
        _children.Add(component);
    }
    
    public void Remove(FileSystemComponent component)
    {
        _children.Remove(component);
    }
    
    public override void Display(int indent = 0)
    {
        Console.WriteLine($"{new string(' ', indent)}📁 {Name} (Total: {Size} bytes)");
        
        foreach (var child in _children)
        {
            child.Display(indent + 2);
        }
    }
}

// Использование
var root = new Directory("Root");
var documents = new Directory("Documents");
var images = new Directory("Images");

documents.Add(new File("resume.pdf", 2048));
documents.Add(new File("contract.docx", 4096));

images.Add(new File("photo1.jpg", 1024 * 1024));
images.Add(new File("photo2.png", 2048 * 1024));

root.Add(documents);
root.Add(images);
root.Add(new File("readme.txt", 512));

root.Display();
// Вывод:
// 📁 Root (Total: 3149824 bytes)
//   📁 Documents (Total: 6144 bytes)
//     📄 resume.pdf (2048 bytes)
//     📄 contract.docx (4096 bytes)
//   📁 Images (Total: 3143680 bytes)
//     📄 photo1.jpg (1048576 bytes)
//     📄 photo2.png (2095104 bytes)
//   📄 readme.txt (512 bytes)

Поведенческие паттерны: взаимодействие объектов

3.1. Strategy (Стратегия) — выбор алгоритма во время выполнения

3.1.1. Стратегия для различных алгоритмов сортировки

public interface ISortStrategy<T>
{
    IEnumerable<T> Sort(IEnumerable<T> collection);
}

// Конкретные стратегии
public class QuickSortStrategy<T> : ISortStrategy<T> where T : IComparable<T>
{
    public IEnumerable<T> Sort(IEnumerable<T> collection)
    {
        var list = collection.ToList();
        QuickSort(list, 0, list.Count - 1);
        return list;
    }
    
    private void QuickSort(List<T> list, int left, int right)
    {
        if (left < right)
        {
            int pivot = Partition(list, left, right);
            QuickSort(list, left, pivot - 1);
            QuickSort(list, pivot + 1, right);
        }
    }
    
    private int Partition(List<T> list, int left, int right)
    {
        T pivot = list[right];
        int i = left - 1;
        
        for (int j = left; j < right; j++)
        {
            if (list[j].CompareTo(pivot) <= 0)
            {
                i++;
                (list[i], list[j]) = (list[j], list[i]);
            }
        }
        
        (list[i + 1], list[right]) = (list[right], list[i + 1]);
        return i + 1;
    }
}

public class MergeSortStrategy<T> : ISortStrategy<T> where T : IComparable<T>
{
    public IEnumerable<T> Sort(IEnumerable<T> collection)
    {
        var list = collection.ToList();
        if (list.Count <= 1) return list;
        
        return MergeSort(list);
    }
    
    private List<T> MergeSort(List<T> list)
    {
        if (list.Count <= 1) return list;
        
        var middle = list.Count / 2;
        var left = list.Take(middle).ToList();
        var right = list.Skip(middle).ToList();
        
        return Merge(MergeSort(left), MergeSort(right));
    }
    
    private List<T> Merge(List<T> left, List<T> right)
    {
        var result = new List<T>();
        int leftIndex = 0, rightIndex = 0;
        
        while (leftIndex < left.Count && rightIndex < right.Count)
        {
            if (left[leftIndex].CompareTo(right[rightIndex]) <= 0)
            {
                result.Add(left[leftIndex++]);
            }
            else
            {
                result.Add(right[rightIndex++]);
            }
        }
        
        while (leftIndex < left.Count) result.Add(left[leftIndex++]);
        while (rightIndex < right.Count) result.Add(right[rightIndex++]);
        
        return result;
    }
}

// Контекст, использующий стратегию
public class Sorter<T> where T : IComparable<T>
{
    private ISortStrategy<T> _strategy;
    
    public Sorter(ISortStrategy<T> strategy)
    {
        _strategy = strategy;
    }
    
    public void SetStrategy(ISortStrategy<T> strategy)
    {
        _strategy = strategy;
    }
    
    public IEnumerable<T> ExecuteSort(IEnumerable<T> collection)
    {
        Console.WriteLine($"Using {_strategy.GetType().Name}");
        return _strategy.Sort(collection);
    }
}

// Использование с лямбда-выражениями (альтернатива интерфейсу)
public class FlexibleSorter<T>
{
    private Func<IEnumerable<T>, IEnumerable<T>> _sortAlgorithm;
    
    public FlexibleSorter(Func<IEnumerable<T>, IEnumerable<T>> sortAlgorithm)
    {
        _sortAlgorithm = sortAlgorithm;
    }
    
    public IEnumerable<T> Sort(IEnumerable<T> collection) => _sortAlgorithm(collection);
}

// Регистрация стратегий в DI
services.AddTransient<ISortStrategy<int>, QuickSortStrategy<int>>();
services.AddTransient<ISortStrategy<int>, MergeSortStrategy<int>>();
services.AddTransient<ISortStrategy<string>, QuickSortStrategy<string>>();

// Использование
var data = new[] { 5, 3, 8, 1, 9, 2 };

var sorter = new Sorter<int>(new QuickSortStrategy<int>());
var sorted1 = sorter.ExecuteSort(data);

sorter.SetStrategy(new MergeSortStrategy<int>());
var sorted2 = sorter.ExecuteSort(data);

// Использование лямбда-выражения
var lambdaSorter = new FlexibleSorter<int>(collection => 
    collection.OrderBy(x => x).ToList());
var sorted3 = lambdaSorter.Sort(data);

3.2. Observer (Наблюдатель) — современная реализация через события

3.2.1. Система уведомлений с событиями C#

// Событийная модель (классическая для C#)
public class StockMarket
{
    // Событие с использованием EventHandler<T>
    public event EventHandler<StockPriceChangedEventArgs> StockPriceChanged;
    
    private readonly Dictionary<string, decimal> _stockPrices = new();
    
    public void UpdateStockPrice(string symbol, decimal price)
    {
        var oldPrice = _stockPrices.GetValueOrDefault(symbol);
        _stockPrices[symbol] = price;
        
        // Генерация события
        OnStockPriceChanged(new StockPriceChangedEventArgs(symbol, oldPrice, price));
    }
    
    protected virtual void OnStockPriceChanged(StockPriceChangedEventArgs e)
    {
        StockPriceChanged?.Invoke(this, e);
    }
}

public class StockPriceChangedEventArgs : EventArgs
{
    public string Symbol { get; }
    public decimal OldPrice { get; }
    public decimal NewPrice { get; }
    public decimal ChangePercent => OldPrice != 0 
        ? ((NewPrice - OldPrice) / OldPrice) * 100 
        : 0;
    
    public StockPriceChangedEventArgs(string symbol, decimal oldPrice, decimal newPrice)
    {
        Symbol = symbol;
        OldPrice = oldPrice;
        NewPrice = newPrice;
    }
}

// Подписчики (наблюдатели)
public class Trader
{
    private readonly string _name;
    
    public Trader(string name)
    {
        _name = name;
    }
    
    public void Subscribe(StockMarket market)
    {
        market.StockPriceChanged += OnStockPriceChanged;
    }
    
    public void Unsubscribe(StockMarket market)
    {
        market.StockPriceChanged -= OnStockPriceChanged;
    }
    
    private void OnStockPriceChanged(object sender, StockPriceChangedEventArgs e)
    {
        Console.WriteLine($"[{_name}] {e.Symbol}: {e.OldPrice:C} -> {e.NewPrice:C} " +
                         $"({e.ChangePercent:+#.##;-#.##}%)");
        
        if (Math.Abs(e.ChangePercent) > 5)
        {
            Console.WriteLine($"[{_name}] ALERT: Significant change in {e.Symbol}!");
        }
    }
}

public class StockAnalyzer
{
    private readonly Dictionary<string, List<decimal>> _priceHistory = new();
    
    public void Subscribe(StockMarket market)
    {
        market.StockPriceChanged += OnStockPriceChanged;
    }
    
    private void OnStockPriceChanged(object sender, StockPriceChangedEventArgs e)
    {
        if (!_priceHistory.ContainsKey(e.Symbol))
        {
            _priceHistory[e.Symbol] = new List<decimal>();
        }
        
        _priceHistory[e.Symbol].Add(e.NewPrice);
        
        if (_priceHistory[e.Symbol].Count >= 10)
        {
            var recentPrices = _priceHistory[e.Symbol].TakeLast(10);
            var average = recentPrices.Average();
            var volatility = CalculateVolatility(recentPrices);
            
            Console.WriteLine($"[Analyzer] {e.Symbol}: " +
                             $"10-period avg: {average:C}, volatility: {volatility:P2}");
        }
    }
    
    private decimal CalculateVolatility(IEnumerable<decimal> prices)
    {
        var priceList = prices.ToList();
        var mean = priceList.Average();
        var variance = priceList.Select(p => Math.Pow((double)(p - mean), 2)).Average();
        return (decimal)Math.Sqrt(variance) / (mean != 0 ? mean : 1);
    }
}

// Использование
var market = new StockMarket();
var trader1 = new Trader("John");
var trader2 = new Trader("Alice");
var analyzer = new StockAnalyzer();

trader1.Subscribe(market);
trader2.Subscribe(market);
analyzer.Subscribe(market);

market.UpdateStockPrice("AAPL", 150.00m);
market.UpdateStockPrice("AAPL", 155.50m);
market.UpdateStockPrice("GOOGL", 2800.00m);
market.UpdateStockPrice("AAPL", 145.00m); // Падение более 5%

// Результат:
// [John] AAPL: $0.00 -> $150.00 (+0%)
// [Alice] AAPL: $0.00 -> $150.00 (+0%)
// [John] AAPL: $150.00 -> $155.50 (+3.67%)
// [Alice] AAPL: $150.00 -> $155.50 (+3.67%)
// [John] GOOGL: $0.00 -> $2,800.00 (+0%)
// [Alice] GOOGL: $0.00 -> $2,800.00 (+0%)
// [John] AAPL: $155.50 -> $145.00 (-6.75%)
// [John] ALERT: Significant change in AAPL!
// [Alice] AAPL: $155.50 -> $145.00 (-6.75%)
// [Alice] ALERT: Significant change in AAPL!

3.3. Command (Команда) — отмена и повтор операций

3.3.1. Система отмены действий в графическом редакторе

public interface ICommand
{
    void Execute();
    void Undo();
}

// Конкретные команды
public class AddShapeCommand : ICommand
{
    private readonly Canvas _canvas;
    private readonly Shape _shape;
    private bool _isExecuted = false;
    
    public AddShapeCommand(Canvas canvas, Shape shape)
    {
        _canvas = canvas;
        _shape = shape;
    }
    
    public void Execute()
    {
        if (!_isExecuted)
        {
            _canvas.AddShape(_shape);
            _isExecuted = true;
        }
    }
    
    public void Undo()
    {
        if (_isExecuted)
        {
            _canvas.RemoveShape(_shape);
            _isExecuted = false;
        }
    }
}

public class MoveShapeCommand : ICommand
{
    private readonly Canvas _canvas;
    private readonly Shape _shape;
    private readonly (int X, int Y) _oldPosition;
    private readonly (int X, int Y) _newPosition;
    private bool _isExecuted = false;
    
    public MoveShapeCommand(Canvas canvas, Shape shape, int newX, int newY)
    {
        _canvas = canvas;
        _shape = shape;
        _oldPosition = (shape.X, shape.Y);
        _newPosition = (newX, newY);
    }
    
    public void Execute()
    {
        if (!_isExecuted)
        {
            _shape.Move(_newPosition.X, _newPosition.Y);
            _isExecuted = true;
        }
    }
    
    public void Undo()
    {
        if (_isExecuted)
        {
            _shape.Move(_oldPosition.X, _oldPosition.Y);
            _isExecuted = false;
        }
    }
}

// Примитивы
public class Shape
{
    public int X { get; private set; }
    public int Y { get; private set; }
    public string Color { get; set; }
    
    public Shape(int x, int y, string color)
    {
        X = x;
        Y = y;
        Color = color;
    }
    
    public void Move(int x, int y)
    {
        X = x;
        Y = y;
        Console.WriteLine($"Moved shape to ({X}, {Y})");
    }
    
    public void Draw()
    {
        Console.WriteLine($"Drawing {Color} shape at ({X}, {Y})");
    }
}

public class Canvas
{
    private readonly List<Shape> _shapes = new();
    
    public void AddShape(Shape shape)
    {
        _shapes.Add(shape);
        Console.WriteLine($"Added shape at ({shape.X}, {shape.Y})");
    }
    
    public void RemoveShape(Shape shape)
    {
        _shapes.Remove(shape);
        Console.WriteLine($"Removed shape from ({shape.X}, {shape.Y})");
    }
    
    public void Render()
    {
        Console.WriteLine("\n--- Canvas ---");
        foreach (var shape in _shapes)
        {
            shape.Draw();
        }
        Console.WriteLine("--------------\n");
    }
}

// Инвокер (вызывающий объект) с историей команд
public class CommandManager
{
    private readonly Stack<ICommand> _undoStack = new();
    private readonly Stack<ICommand> _redoStack = new();
    
    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _undoStack.Push(command);
        _redoStack.Clear(); // Новая команда очищает историю повторов
    }
    
    public void Undo()
    {
        if (_undoStack.Count > 0)
        {
            var command = _undoStack.Pop();
            command.Undo();
            _redoStack.Push(command);
        }
        else
        {
            Console.WriteLine("Nothing to undo");
        }
    }
    
    public void Redo()
    {
        if (_redoStack.Count > 0)
        {
            var command = _redoStack.Pop();
            command.Execute();
            _undoStack.Push(command);
        }
        else
        {
            Console.WriteLine("Nothing to redo");
        }
    }
    
    public void PrintHistory()
    {
        Console.WriteLine($"Undo stack: {_undoStack.Count} commands");
        Console.WriteLine($"Redo stack: {_redoStack.Count} commands");
    }
}

// Использование
var canvas = new Canvas();
var commandManager = new CommandManager();

var shape1 = new Shape(10, 10, "Red");
var shape2 = new Shape(50, 50, "Blue");

// Выполнение команд
commandManager.ExecuteCommand(new AddShapeCommand(canvas, shape1));
canvas.Render();

commandManager.ExecuteCommand(new AddShapeCommand(canvas, shape2));
canvas.Render();

commandManager.ExecuteCommand(new MoveShapeCommand(canvas, shape1, 20, 20));
canvas.Render();

// Отмена
Console.WriteLine("\n--- Undo last command ---");
commandManager.Undo();
canvas.Render();

Console.WriteLine("\n--- Undo another command ---");
commandManager.Undo();
canvas.Render();

// Повтор
Console.WriteLine("\n--- Redo ---");
commandManager.Redo();
canvas.Render();

commandManager.PrintHistory();

Современные подходы и антипаттерны

4.1. Паттерны в функциональном стиле C#

// Функциональная реализация стратегии
public static class FunctionalStrategy
{
    public static IEnumerable<T> Sort<T>(
        IEnumerable<T> collection, 
        Func<IEnumerable<T>, IEnumerable<T>> strategy) 
        => strategy(collection);
    
    // Каррирование стратегий
    public static Func<IEnumerable<T>, IEnumerable<T>> QuickSortStrategy<T>() 
        where T : IComparable<T>
    {
        return collection =>
        {
            var list = collection.ToList();
            QuickSort(list, 0, list.Count - 1);
            return list;
        };
        
        void QuickSort(List<T> list, int left, int right) { /* реализация */ }
    }
}

// Использование
var data = new[] { 3, 1, 4, 1, 5, 9 };
var sorted = FunctionalStrategy.Sort(data, FunctionalStrategy.QuickSortStrategy<int>());

4.2. Антипаттерны и неправильное использование

// АНТИПАТТЕРН: God Object Singleton
public class ApplicationManager // Плохо!
{
    public static ApplicationManager Instance { get; } = new ApplicationManager();
    
    public void ProcessOrder() { /* ... */ }
    public void SendEmail() { /* ... */ }
    public void UpdateDatabase() { /* ... */ }
    public void GenerateReport() { /* ... */ }
    // Десятки несвязанных методов...
}

// АНТИПАТТЕРН: Переусложненный паттерн
public class OverEngineeredFactory
{
    private readonly Dictionary<Type, Func<object>> _factories = new();
    
    public void Register<T>(Func<T> factory) => _factories[typeof(T)] = () => factory();
    
    public T Create<T>()
    {
        if (_factories.TryGetValue(typeof(T), out var factory))
            return (T)factory();
        
        throw new InvalidOperationException($"No factory for {typeof(T)}");
    }
    
    // Слишком сложно для простых случаев!
}

// ПРАВИЛЬНЫЙ ПОДХОД: KISS (Keep It Simple, Stupid)
public class SimpleServiceFactory
{
    public T Create<T>() where T : new() => new T();
}

4.3. Паттерны в асинхронном контексте

public interface IAsyncCommand
{
    Task ExecuteAsync(CancellationToken cancellationToken);
    Task UndoAsync(CancellationToken cancellationToken);
}

public class AsyncSaveCommand : IAsyncCommand
{
    private readonly IDataRepository _repository;
    private readonly Data _data;
    private Data _backup;
    
    public AsyncSaveCommand(IDataRepository repository, Data data)
    {
        _repository = repository;
        _data = data;
    }
    
    public async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        _backup = await _repository.GetByIdAsync(_data.Id, cancellationToken);
        await _repository.SaveAsync(_data, cancellationToken);
    }
    
    public async Task UndoAsync(CancellationToken cancellationToken)
    {
        if (_backup != null)
        {
            await _repository.SaveAsync(_backup, cancellationToken);
        }
    }
}

// Асинхронная фабрика
public class AsyncFactory
{
    public async Task<T> CreateAsync<T>() where T : IInitializable, new()
    {
        var instance = new T();
        await instance.InitializeAsync();
        return instance;
    }
}

Заключение

Паттерны проектирования в C# — это не догма, а гибкий инструментарий, который должен применяться осознанно и в соответствии с конкретными задачами проекта.

Для дальнейшего изучения

  • Domain-Driven Design (DDD): Паттерны предметной области — Entity, Value Object, Aggregate, Repository, Domain Service.

  • Микросервисные паттерны: Saga, CQRS, API Gateway, Circuit Breaker, Service Discovery.

  • Архитектурные стили: Clean Architecture, Hexagonal Architecture, Onion Architecture.

  • Реактивные паттерны: Reactive Extensions (Rx.NET), Message Queue patterns.

  • Оптимизация производительности: Паттерны кэширования, пулинга объектов, ленивой загрузки.

  • Тестирование: Паттерны для unit-тестов (Test Doubles, Mock, Stub, Fake).

  • Контейнеры зависимостей: Регистрация паттернов в DI-контейнерах (Scoped, Singleton, Transient).

  • Source Generators: Автоматическая генерация кода для часто используемых паттернов.

  • Records и паттерны: Использование C# 9+ records для реализации Value Objects.

  • Паттерны параллельного программирования: Producer-Consumer, Reader-Writer, Barrier, Fork-Join.