Паттерны проектирования в C# с примерами: от А до Я

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

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

Начать курс

Паттерны проектирования в C# с примерами: от А до Я

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

В этой статье мы разберем три основные группы паттернов по классификации GoF (Gang of Four): порождающие, структурные и поведенческие. Каждый паттерн будет рассмотрен на практическом примере на C# (.NET).

Порождающие паттерны (Creational Patterns)

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

Одиночка (Singleton)

Гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В C# для безопасной работы в многопоточной среде используется Lazy<T>.

public sealed class Logger
{
    private static readonly Lazy<Logger> _instance = 
        new Lazy<Logger>(() => new Logger());

    public static Logger Instance => _instance.Value;

    private Logger() { }

    public void Log(string message)
    {
        Console.WriteLine($"[{DateTime.Now}] {message}");
    }
}

// Использование
Logger.Instance.Log("Приложение запущено");

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

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

public interface ITransport
{
    void Deliver();
}

public class Truck : ITransport
{
    public void Deliver() => Console.WriteLine("Доставка по земле");
}

public class Ship : ITransport
{
    public void Deliver() => Console.WriteLine("Доставка по морю");
}

public abstract class Logistics
{
    public abstract ITransport CreateTransport();

    public void PlanDelivery()
    {
        var transport = CreateTransport();
        transport.Deliver();
    }
}

public class RoadLogistics : Logistics
{
    public override ITransport CreateTransport() => new Truck();
}

// Использование
Logistics logistics = new RoadLogistics();
logistics.PlanDelivery(); // Вывод: Доставка по земле

Структурные паттерны (Structural Patterns)

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

Адаптер (Adapter)

Позволяет объектам с несовместимыми интерфейсами работать вместе. Часто используется при интеграции со старым кодом или сторонними библиотеками.

// Целевой интерфейс, ожидаемый клиентом
public interface IModernPrinter
{
    void Print(string text);
}

// Старый класс с несовместимым интерфейсом
public class OldPrinter
{
    public void PrintOldStyle(string message)
    {
        Console.WriteLine($"Старый принтер: {message}");
    }
}

// Адаптер
public class PrinterAdapter : IModernPrinter
{
    private readonly OldPrinter _oldPrinter;

    public PrinterAdapter(OldPrinter oldPrinter)
    {
        _oldPrinter = oldPrinter;
    }

    public void Print(string text)
    {
        _oldPrinter.PrintOldStyle(text);
    }
}

// Использование
IModernPrinter printer = new PrinterAdapter(new OldPrinter());
printer.Print("Привет, мир!"); // Вывод: Старый принтер: Привет, мир!

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

Динамически добавляет объекту новые обязанности, не изменяя его код. Является гибкой альтернативой наследованию.

public interface ICoffee
{
    double GetCost();
    string GetDescription();
}

public class SimpleCoffee : ICoffee
{
    public double GetCost() => 10.0;
    public string GetDescription() => "Обычный кофе";
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;

    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;

    public virtual double GetCost() => _coffee.GetCost();
    public virtual string GetDescription() => _coffee.GetDescription();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }

    public override double GetCost() => base.GetCost() + 2.5;
    public override string GetDescription() => base.GetDescription() + ", с молоком";
}

// Использование
ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
Console.WriteLine($"{coffee.GetDescription()}: {coffee.GetCost()} руб."); 
// Вывод: Обычный кофе, с молоком: 12.5 руб.

Поведенческие паттерны (Behavioral Patterns)

Эти паттерны определяют алгоритмы и распределение ответственности между объектами.

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

Создает механизм подписки, позволяющий одним объектам (наблюдателям) следить за изменениями состояния других объектов (субъектов). В C# часто реализуется через события.

public class Stock
{
    private decimal _price;
    private readonly List<IInvestor> _investors = new();

    public void Attach(IInvestor investor) => _investors.Add(investor);
    public void Detach(IInvestor investor) => _investors.Remove(investor);

    public decimal Price
    {
        get => _price;
        set
        {
            if (_price != value)
            {
                _price = value;
                Notify();
            }
        }
    }

    private void Notify()
    {
        foreach (var investor in _investors)
            investor.Update(this);
    }
}

public interface IInvestor
{
    void Update(Stock stock);
}

public class Investor : IInvestor
{
    public string Name { get; set; }

    public void Update(Stock stock)
    {
        Console.WriteLine($"Уведомление для {Name}: цена изменилась на {stock.Price}");
    }
}

// Использование
var stock = new Stock();
var investor1 = new Investor { Name = "Иван" };
stock.Attach(investor1);
stock.Price = 150.5m; // Уведомление для Иван: цена изменилась на 150,5

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

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

public interface ISortStrategy
{
    void Sort(List<int> list);
}

public class QuickSort : ISortStrategy
{
    public void Sort(List<int> list)
    {
        list.Sort(); // Упрощенная реализация
        Console.WriteLine("Быстрая сортировка выполнена                

Блоги

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