Паттерны проектирования в C#: когда и зачем их применять

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

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

Начать курс

Паттерны проектирования в C#: не просто модные слова, а ваш инструмент выживания

Представьте: вы пишете код на C#. Проект растёт. Сначала всё чисто и понятно. А через месяц вы с ужасом смотрите на свою же архитектуру, где всё завязано друг на друга, и новое изменение ломает три старых модуля. Знакомо? Если да — вы столкнулись с проблемой, которую успешно решают паттерны проектирования в C#.

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

Что такое паттерны проектирования и зачем они нужны в C#?

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

Основные причины использовать шаблоны проектирования C#:

  • Упрощение коммуникации — вы говорите коллеге «я использую здесь Observer», и ему сразу понятна архитектура.
  • Повышение переиспользуемости — меньше дублирования, больше модульности.
  • Гибкость и тестируемость — код легко менять и покрывать юнит-тестами.
  • Защита от «спагетти-кода» — структура становится предсказуемой.

Но помните: паттерны — не серебряная пуля. Иногда проще написать 10 строк без паттерна, чем разворачивать фабрику из 4 классов.

Когда применять паттерны, а когда — нет?

Золотое правило: паттерн должен решать вашу проблему, а не создавать новую. Если вы не знаете, зачем вам Singleton — он вам не нужен.

Когда стоит внедрять паттерны GoF C#:

  • Вы видите повторяющуюся проблему в архитектуре.
  • Код становится трудно поддерживать из-за сильной связанности.
  • Вы ожидаете, что проект будет расти и меняться.

Когда лучше не надо:

  • Прототип или маленькая утилита на 200 строк — не усложняйте.
  • Вы не понимаете, зачем конкретный паттерн — не используйте.
  • Паттерн добавляет больше кода, чем решает проблему — упростите.

Топ-5 паттернов проектирования в C# с примерами

Рассмотрим самые популярные архитектурные паттерны C# и реальные сценарии их применения.

1. Singleton (Одиночка) — когда нужен один экземпляр

Когда использовать: у вас должен быть ровно один объект класса (логгер, конфигурация, кэш).

Реализация на C# (потокобезопасная):

public sealed class ConfigManager
{
    private static ConfigManager _instance = null;
    private static readonly object _lock = new object();

    private ConfigManager() { }

    public static ConfigManager Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new ConfigManager();
                }
                return _instance;
            }
        }
    }

    public string GetSetting(string key)
    {
        // логика получения настройки
        return "value";
    }
}

Использование:

var config = ConfigManager.Instance;
string apiUrl = config.GetSetting("ApiUrl");

Это классический пример паттерна Singleton в C#. Но не злоупотребляйте — он усложняет юнит-тестирование.

2. Factory Method (Фабричный метод) — создание объектов без привязки к конкретному классу

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

Пример: создание уведомлений

public abstract class NotificationFactory
{
    public abstract INotification CreateNotification();
}

public class EmailNotificationFactory : NotificationFactory
{
    public override INotification CreateNotification()
    {
        return new EmailNotification();
    }
}

public class SmsNotificationFactory : NotificationFactory
{
    public override INotification CreateNotification()
    {
        return new SmsNotification();
    }
}

public interface INotification
{
    void Send(string message);
}

public class EmailNotification : INotification
{
    public void Send(string message) => Console.WriteLine($"Email: {message}");
}

public class SmsNotification : INotification
{
    public void Send(string message) => Console.WriteLine($"SMS: {message}");
}

Использование:

NotificationFactory factory = new EmailNotificationFactory();
INotification notification = factory.CreateNotification();
notification.Send("Привет!");

Паттерны Factory Method в C# отлично работают в связке с Dependency Injection.

3. Observer (Наблюдатель) — реакция на события

Когда использовать: при изменении состояния одного объекта нужно уведомить множество других.

Пример: система подписки на новости

public interface IObserver
{
    void Update(string news);
}

public class NewsPublisher
{
    private List<IObserver> _observers = new List<IObserver>();

    public void Subscribe(IObserver observer) => _observers.Add(observer);
    public void Unsubscribe(IObserver observer) => _observers.Remove(observer);

    public void PublishNews(string news)
    {
        foreach (var observer in _observers)
        {
            observer.Update(news);
        }
    }
}

public class EmailSubscriber : IObserver
{
    private string _email;
    public EmailSubscriber(string email) => _email = email;

    public void Update(string news) 
        => Console.WriteLine($"Уведомление на {_email}: {news}");
}

Использование:

var publisher = new NewsPublisher();
var subscriber = new EmailSubscriber("user@example.com");
publisher.Subscribe(subscriber);
publisher.PublishNews("Вышла новая версия C# 12");

В C# этот паттерн часто реализуют через события (event) и делегаты — это встроенный механизм Observer в .NET.

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

Когда использовать: у вас есть несколько вариантов поведения (алгоритмов), и они должны быть взаимозаменяемы.

Пример: расчёт стоимости доставки

public interface IShippingStrategy
{
    decimal Calculate(decimal orderPrice);
}

public class ExpressShipping : IShippingStrategy
{
    public decimal Calculate(decimal orderPrice) => orderPrice * 0.15m;
}

public class StandardShipping : IShippingStrategy
{
    public decimal Calculate(decimal orderPrice) => orderPrice * 0.05m;
}

public class Order
{
    private IShippingStrategy _shippingStrategy;

    public Order(IShippingStrategy shippingStrategy)
    {
        _shippingStrategy = shippingStrategy                

Блоги

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