🔷 C#

Паттерны проектирования в C# (.NET): подробное руководство

Что это такое и зачем? (аналогия из реальной жизни)

Представьте, что вы строите дом. Если вы каждый раз будете начинать строительство с нуля — рыть котлован, замешивать бетон, пилить доски — на один дом уйдут годы. Но если у вас есть проверенные чертежи (типовые проекты домов), вы просто берёте готовое решение: «здесь будет несущая стена, здесь — окно, а крыша — двускатная». Вы не изобретаете велосипед, а используете опыт тысяч строителей до вас.

В программировании то же самое. Паттерны проектирования (или просто «паттерны») — это готовые, проверенные временем решения типовых задач. Это не готовый код, который можно скопировать, а скорее шаблон мысли, способ организации классов и объектов, чтобы код был:

  • Понятным (другие разработчики сразу догадаются, что вы хотели сделать)
  • Гибким (легко добавлять новые функции, не ломая старые)
  • Переиспользуемым (один раз написали — используете в сотне проектов)
Важно: Паттерны — это не магия и не обязательное требование. Это инструмент. Как молоток: если вы забиваете гвозди — он незаменим, но если вы хотите закрутить шуруп — лучше взять отвёртку. Используйте паттерны только тогда, когда они действительно решают проблему.

Аналогия из жизни: Представьте, что вы — шеф-повар. У вас есть набор базовых рецептов: «как сварить бульон», «как сделать соус бешамель», «как замесить тесто». Каждый раз, когда приходит заказ, вы не придумываете новый способ варки бульона — вы берёте готовый рецепт и немного адаптируете его под конкретное блюдо. Паттерны — это такие же «рецепты» для программиста.

Всего существует 23 классических паттерна, описанных в книге «Банды четырёх» (Gang of Four). Они делятся на три группы:

  • Порождающие — как удобно создавать объекты (например, не через new, а через специальный метод)
  • Структурные — как собирать классы в большие конструкции (как из кирпичиков собрать стену)
  • Поведенческие — как объекты общаются друг с другом (как люди в офисе передают друг другу документы)

Теория: как это работает изнутри

Давайте заглянем «под капот» самого популярного паттерна — Одиночка (Singleton). Он нужен, когда у вас в программе должен быть ровно один экземпляр какого-то класса. Например, подключение к базе данных, логгер (запись ошибок) или настройки приложения.

Как это работает изнутри:

  1. Конструктор класса делается закрытым (private). Это значит, что никто снаружи не может написать new MyClass().
  2. Создаётся статическое поле, которое хранит единственный экземпляр класса.
  3. Создаётся статический метод (обычно называется GetInstance() или свойство Instance), который проверяет: если экземпляр уже есть — вернуть его, если нет — создать новый и вернуть.

Вот как это выглядит на схеме:

┌─────────────────────────┐
│        Singleton         │
├─────────────────────────┤
│ - instance: Singleton    │  ← статическое поле (единственный экземпляр)
├─────────────────────────┤
│ - Singleton()            │  ← закрытый конструктор
│ + GetInstance(): Singleton│  ← статический метод доступа
└─────────────────────────┘

Когда вы вызываете Singleton.GetInstance() в первый раз, создаётся объект. Во второй, третий, сотый раз — возвращается тот же самый объект. Это как если бы у вас был один ключ от сейфа: вы можете сделать сколько угодно копий ключа (вызовов метода), но сейф (объект) остаётся один.

Важно: В многопоточных программах (когда несколько частей кода работают одновременно) обычный Singleton может сломаться — два потока могут одновременно проверить, что экземпляра нет, и создать два объекта. Для этого используют «блокировку» (lock) — мы рассмотрим это в примерах.

Синтаксис в C# (.NET): все формы и варианты

В C# есть несколько способов реализовать паттерны. Давайте разберём основные формы на примере всё того же Singleton, а потом перейдём к другим паттернам.

1. Простой Singleton (не потокобезопасный)

public class SimpleSingleton
{
    // Статическое поле для хранения единственного экземпляра
    private static SimpleSingleton _instance;
    
    // Закрытый конструктор — никто не сможет создать объект через new
    private SimpleSingleton() 
    {
        Console.WriteLine("Создан новый экземпляр SimpleSingleton");
    }
    
    // Статическое свойство для доступа к экземпляру
    public static SimpleSingleton Instance
    {
        get
        {
            // Если экземпляр ещё не создан — создаём
            if (_instance == null)
            {
                _instance = new SimpleSingleton();
            }
            // Возвращаем существующий экземпляр
            return _instance;
        }
    }
    
    public void DoSomething()
    {
        Console.WriteLine("Работает единственный экземпляр");
    }
}

2. Потокобезопасный Singleton с блокировкой (double-check locking)

public class ThreadSafeSingleton
{
    // Объект для блокировки (замок)
    private static readonly object _lock = new object();
    private static ThreadSafeSingleton _instance;
    
    private ThreadSafeSingleton() { }
    
    public static ThreadSafeSingleton Instance
    {
        get
        {
            // Первая проверка — без блокировки (быстрая)
            if (_instance == null)
            {
                // Блокируем доступ для других потоков
                lock (_lock)
                {
                    // Вторая проверка — уже внутри блокировки
                    if (_instance == null)
                    {
                        _instance = new ThreadSafeSingleton();
                    }
                }
            }
            return _instance;
        }
    }
}

3. Singleton через Lazy (самый простой и безопасный)

public class LazySingleton
{
    // Lazy сам заботится о потокобезопасности
    private static readonly Lazy<LazySingleton> _lazyInstance = 
        new Lazy<LazySingleton>(() => new LazySingleton());
    
    private LazySingleton() { }
    
    public static LazySingleton Instance => _lazyInstance.Value;
}

Теперь давайте посмотрим на другие популярные паттерны и их синтаксис в C#.

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

// Абстрактный класс (шаблон) для всех видов транспорта
public abstract class Transport
{
    public abstract void Move();
}

// Конкретные виды транспорта
public class Car : Transport
{
    public override void Move() => Console.WriteLine("Машина едет по дороге");
}

public class Ship : Transport
{
    public override void Move() => Console.WriteLine("Корабль плывёт по морю");
}

// Фабрика — создаёт нужный транспорт
public class TransportFactory
{
    public Transport CreateTransport(string type)
    {
        switch (type.ToLower())
        {
            case "car":
                return new Car();
            case "ship":
                return new Ship();
            default:
                throw new ArgumentException("Неизвестный тип транспорта");
        }
    }
}

5. Строитель (Builder)

// Класс, который мы будем собирать по частям
public class Pizza
{
    public string Dough { get; set; }
    public string Sauce { get; set; }
    public string Topping { get; set; }
    
    public void Display()
    {
        Console.WriteLine($"Пицца: тесто - {Dough}, соус - {Sauce}, топпинг - {Topping}");
    }
}

// Строитель — пошагово собирает пиццу
public class PizzaBuilder
{
    private Pizza _pizza = new Pizza();
    
    public PizzaBuilder SetDough(string dough)
    {
        _pizza.Dough = dough;
        return this; // Возвращаем себя для цепочки вызовов
    }
    
    public PizzaBuilder SetSauce(string sauce)
    {
        _pizza.Sauce = sauce;
        return this;
    }
    
    public PizzaBuilder SetTopping(string topping)
    {
        _pizza.Topping = topping;
        return this;
    }
    
    public Pizza Build()
    {
        return _pizza;
    }
}

// Использование:
// Pizza myPizza = new PizzaBuilder()
//     .SetDough("тонкое")
//     .SetSauce("томатный")
//     .SetTopping("сыр")
//     .Build();

6. Адаптер (Adapter)

// Старый интерфейс (например, для зарядки micro-USB)
public interface IMicroUsb
{
    void Charge();
}

// Старое устройство
public class OldPhone : IMicroUsb
{
    public void Charge() => Console.WriteLine("Заряжается через micro-USB");
}

// Новый интерфейс (USB-C)
public interface IUsbC
{
    void FastCharge();
}

// Адаптер — превращает micro-USB в USB-C
public class UsbCAdapter : IUsbC
{
    private IMicroUsb _microUsbDevice;
    
    public UsbCAdapter(IMicroUsb device)
    {
        _microUsbDevice = device;
    }
    
    public void FastCharge()
    {
        Console.WriteLine("Адаптер преобразует сигнал...");
        _microUsbDevice.Charge();
    }
}

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

// Интерфейс наблюдателя (подписчика)
public interface IObserver
{
    void Update(string message);
}

// Конкретный наблюдатель
public class User : IObserver
{
    private string _name;
    
    public User(string name) => _name = name;
    
    public void Update(string message)
    {
        Console.WriteLine($"{_name} получил уведомление: {message}");
    }
}

// Издатель (наблюдаемый объект)
public class NewsPublisher
{
    private List<IObserver> _observers = new List<IObserver>();
    
    public void Subscribe(IObserver observer)
    {
        _observers.Add(observer);
        Console.WriteLine("Новый подписчик добавлен");
    }
    
    public void Unsubscribe(IObserver observer)
    {
        _observers.Remove(observer);
    }
    
    public void PublishNews(string news)
    {
        Console.WriteLine($"
Опубликована новость: {news}");
        foreach (var observer in _observers)
        {
            observer.Update(news);
        }
    }
}

Примеры кода — от простого к сложному

Пример 1: Singleton для логирования (простой)

using System;
using System.IO;

public class Logger
{
    private static Logger _instance;
    private string _logFilePath;
    
    // Закрытый конструктор — создаём файл лога
    private Logger()
    {
        _logFilePath = "app.log";
        // Очищаем файл при старте
        File.WriteAllText(_logFilePath, $"Лог запущен: {DateTime.Now}
");
    }
    
    public static Logger Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Logger();
            }
            return _instance;
        }
    }
    
    // Метод для записи сообщения в лог
    public void Log(string message)
    {
        string logEntry = $"[{DateTime.Now:HH:mm:ss}] {message}";
        Console.WriteLine(logEntry); // Выводим в консоль
        File.AppendAllText(_logFilePath, logEntry + "
"); // Пишем в файл
    }
}

// Использование:
// Logger.Instance.Log("Программа запущена");
// Logger.Instance.Log("Пользователь нажал кнопку");
// // Второй раз создавать не нужно — используем тот же экземпляр

Пример 2: Фабрика для создания персонажей игры

// Базовый класс персонажа
public abstract class GameCharacter
{
    public string Name { get; set; }
    public int Health { get; set; }
    public abstract void Attack();
}

// Конкретные персонажи
public class Warrior : GameCharacter
{
    public Warrior() 
    { 
        Name = "Воин"; 
        Health = 100; 
    }
    
    public override void Attack()
    {
        Console.WriteLine($"{Name} атакует мечом! (Здоровье: {Health})");
    }
}

public class Mage : GameCharacter
{
    public Mage() 
    { 
        Name = "Маг"; 
        Health = 60; 
    }
    
    public override void Attack()
    {
        Console.WriteLine($"{Name} кидает огненный шар! (Здоровье: {Health})");
    }
}

public class Archer : GameCharacter
{
    public Archer() 
    { 
        Name = "Лучник"; 
        Health = 80; 
    }
    
    public override void Attack()
    {
        Console.WriteLine($"{Name} стреляет из лука! (Здоровье: {Health})");
    }
}

// Фабрика персонажей
public class CharacterFactory
{
    public GameCharacter CreateCharacter(string type)
    {
        // Используем switch expression (C# 8.0+)
        return type.ToLower() switch
        {
            "warrior" => new Warrior(),
            "mage" => new Mage(),
            "archer" => new Archer(),
            _ => throw new ArgumentException($"Неизвестный тип: {type}")
        };
    }
}

// Демонстрация работы
class Program
{
    static void Main()
    {
        var factory = new CharacterFactory();
        
        // Создаём персонажей через фабрику
        var warrior = factory.CreateCharacter("warrior");
        var mage = factory.CreateCharacter("mage");
        var archer = factory.CreateCharacter("archer");
        
        // Все персонажи атакуют по-своему
        warrior.Attack();
        mage.Attack();
        archer.Attack();
    }
}

Пример 3: Строитель для сложного заказа (компьютер)

// Класс компьютера — много параметров
public class Computer
{
    public string CPU { get; set; }
    public string GPU { get; set; }
    public int RAM { get; set; } // в ГБ
    public int Storage { get; set; } // в ГБ
    public bool HasWiFi { get; set; }
    public string OS { get; set; }
    
    public void ShowSpecs()
    {
        Console.WriteLine($"
=== Спецификация компьютера ===");
        Console.WriteLine($"Процессор: {CPU}");
        Console.WriteLine($"Видеокарта: {GPU}");
        Console.WriteLine($"ОЗУ: {RAM} ГБ");
        Console.WriteLine($"Накопитель: {Storage} ГБ");
        Console.WriteLine($"Wi-Fi: {(HasWiFi ? "Да" : "Нет")}");
        Console.WriteLine($"ОС: {OS}");
    }
}

// Строитель — пошагово собирает компьютер
public class ComputerBuilder
{
    private Computer _computer = new Computer();
    
    public ComputerBuilder SetCPU(string cpu)
    {
        _computer.CPU = cpu;
        return this;
    }
    
    public ComputerBuilder SetGPU(string gpu)
    {
        _computer.GPU = gpu;
        return this;
    }
    
    public ComputerBuilder SetRAM(int ram)
    {
        if (ram < 4 || ram > 128)
            throw new ArgumentException("ОЗУ должно быть от 4 до 128 ГБ");
        _computer.RAM = ram;
        return this;
    }
    
    public ComputerBuilder SetStorage(int storage)
    {
        _computer.Storage = storage;
        return this;
    }
    
    public ComputerBuilder EnableWiFi()
    {
        _computer.HasWiFi = true;
        return this;
    }
    
    public ComputerBuilder SetOS(string os)
    {
        _computer.OS = os;
        return this;
    }
    
    public Computer Build()
    {
        // Проверяем, что все обязательные поля заполнены
        if (string.IsNullOrEmpty(_computer.CPU))
            throw new InvalidOperationException("Не указан процессор!");
        if (_computer.RAM == 0)
            throw new InvalidOperationException("Не указана ОЗУ!");
            
        return _computer;
    }
}

// Использование:
// var gamingPC = new ComputerBuilder()
//     .SetCPU("Intel Core i7")
//     .SetGPU("NVIDIA RTX 3080")
//     .SetRAM(32)
//     .SetStorage(1000)
//     .EnableWiFi()
//     .SetOS("Windows 11")
//     .Build();
// gamingPC.ShowSpecs();

Пример 4: Адаптер для работы со старыми и новыми датчиками

// Старый датчик — выдаёт температуру в Фаренгейтах
public class OldSensor
{
    public double GetTemperatureFahrenheit()
    {
        // Имитация показаний датчика
        return 98.6; // Температура тела в F
    }
}

// Новый интерфейс — все датчики должны выдавать Цельсии
public interface INewSensor
{
    double GetTemperatureCelsius();
}

// Адаптер — преобразует Фаренгейты в Цельсии
public class SensorAdapter : INewSensor
{
    private OldSensor _oldSensor;
    
    public SensorAdapter(OldSensor oldSensor)
    {
        _oldSensor = oldSensor;
    }
    
    public double GetTemperatureCelsius()
    {
        // Получаем температуру в Фаренгейтах
        double fahrenheit = _oldSensor.GetTemperatureFahrenheit();
        // Преобразуем в Цельсии
        double celsius = (fahrenheit - 32) * 5.0 / 9.0;
        return Math.Round(celsius, 1);
    }
}

// Новая система, которая работает только с Цельсиями
public class TemperatureMonitor
{
    private INewSensor _sensor;
    
    public TemperatureMonitor(INewSensor sensor)
    {
        _sensor = sensor;
    }
    
    public void ShowTemperature()
    {
        double temp = _sensor.GetTemperatureCelsius();
        Console.WriteLine($"Текущая температура: {temp}°C");
        
        if (temp > 37.5)
            Console.WriteLine("⚠️ Внимание! Высокая температура!");
    }
}

// Демонстрация
class Program
{
    static void Main()
    {
        // У нас есть старый датчик
        var oldSensor = new OldSensor();
        
        // Создаём адаптер, который делает его совместимым с новой системой
        var adapter = new SensorAdapter(oldSensor);
        
        // Новая система работает через адаптер
        var monitor = new TemperatureMonitor(adapter);
        monitor.ShowTemperature();
    }
}

Пример 5: Наблюдатель для системы уведомлений

// Интерфейс наблюдателя
public interface INotificationObserver
{
    void OnNotification(string title, string message);
}

// Конкретные наблюдатели
public class EmailNotifier : INotificationObserver
{
    private string _email;
    
    public EmailNotifier(string email) => _email = email;
    
    public void OnNotification(string title, string message)
    {
        Console.WriteLine($"[EMAIL] Отправлено на {_email}: {title} - {message}");
    }
}

public class SmsNotifier : INotificationObserver
{
    private string _phoneNumber;
    
    public SmsNotifier(string phone) => _phoneNumber = phone;
    
    public void OnNotification(string title, string message)
    {
        Console.WriteLine($"[SMS] Отправлено на {_phoneNumber}: {title} - {message}");
    }
}

public class ConsoleNotifier : INotificationObserver
{
    public void OnNotification(string title, string message)
    {
        Console.WriteLine($"[CONSOLE] {title}: {message}");
    }
}

// Издатель — управляет подписками и рассылкой
public class NotificationService
{
    private List<INotificationObserver> _observers = new List<INotificationObserver>();
    
    // Подписаться на уведомления
    public void Subscribe(INotificationObserver observer)
    {
        _observers.Add(observer);
        Console.WriteLine($"Подписчик добавлен. Всего подписчиков: {_observers.Count}");
    }
    
    // Отписаться от уведомлений
    public void Unsubscribe(INotificationObserver observer)
    {
        _observers.Remove(observer);
        Console.WriteLine($"Подписчик удалён. Осталось: {_observers.Count}");
    }
    
    // Отправить уведомление всем подписчикам
    public void SendNotification(string title, string message)
    {
        Console.WriteLine($"
📢 Отправка уведомления: "{title}"");
        foreach (var observer in _observers)
        {
            observer.OnNotification(title, message);
        }
    }
}

// Демонстрация
class Program
{
    static void Main()
    {
        var notificationService = new NotificationService();
        
        // Создаём подписчиков
        var emailNotifier = new EmailNotifier("user@example.com");
        var smsNotifier = new SmsNotifier("+7-999-123-45-67");
        var consoleNotifier = new ConsoleNotifier();
        
        // Подписываем их
        notificationService.Subscribe(emailNotifier);
        notificationService.Subscribe(smsNotifier);
        notificationService.Subscribe(consoleNotifier);
        
        // Отправляем уведомление
        notificationService.SendNotification("Важное сообщение", "Обновление системы в 23:00");
        
        // Отписываем одного
        notificationService.Unsubscribe(smsNotifier);
        
        // Отправляем ещё одно
        notificationService.SendNotification("Напоминание", "Не забудьте сохранить файлы!");
    }
}

Пример 6: Стратегия (Strategy) — разные способы сортировки

// Интерфейс стратегии сортировки
public interface ISortStrategy
{
    void Sort(int[] array);
}

// Конкретные стратегии
public class BubbleSort : ISortStrategy
{
    public void Sort(int[] array)
    {
        Console.WriteLine("Сортировка пузырьком:");
        int n = array.Length;
        for (int i = 0; i < n - 1; i++)
        {
            for (int j = 0; j < n - i - 1; j++)
            {
                if (array[j] > array[j + 1])
                {
                    // Меняем местами
                    (array[j], array[j + 1]) = (array[j + 1], array[j]);
                }
            }
        }
    }
}

public class QuickSort : ISortStrategy
{
    public void Sort(int[] array)
    {
        Console.WriteLine("Быстрая сортировка:");
        QuickSortRecursive(array, 0, array.Length - 1);
    }
    
    private void QuickSortRecursive(int[] array, int low, int high)
    {
        if (low < high)
        {
            int pi = Partition(array, low, high);
            QuickSortRecursive(array, low, pi - 1);
            QuickSortRecursive(array, pi + 1, high);
        }
    }
    
    private int Partition(int[] array, int low, int high)
    {
        int pivot = array[high];
        int i = low - 1;
        
        for (int j = low; j < high; j++)
        {
            if (array[j] < pivot)
            {
                i++;
                (array[i], array[j]) = (array[j], array[i]);
            }
        }
        (array[i + 1], array[high]) = (array[high], array[i + 1]);
        return i + 1;
    }
}

// Контекст — использует стратегию
public class Sorter
{
    private ISortStrategy _strategy;
    
    // Можно изменить стратегию в любой момент
    public void SetStrategy(ISortStrategy strategy)
    {
        _strategy = strategy;
    }
    
    public void SortArray(int[] array)
    {
        if (_strategy == null)
            throw new InvalidOperationException("Стратегия не установлена!");
        
        // Делаем копию массива, чтобы не портить оригинал
        int[] copy = new int[array.Length];
        Array.Copy(array, copy, array.Length);
        
        _strategy.Sort(copy);
        
        Console.WriteLine($"Результат: [{string.Join(", ", copy)}]
");
    }
}

// Демонстрация
class Program
{
    static void Main()
    {
        int[] numbers = { 64, 34, 25, 12, 22, 11, 90 };
        
        var sorter = new Sorter();
        
        // Используем пузырьковую сортировку
        sorter.SetStrategy(new BubbleSort());
        sorter.SortArray(numbers);
        
        // Меняем стратегию на быструю сортировку
        sorter.SetStrategy(new QuickSort());
        sorter.SortArray(numbers);
    }
}

Пример 7: Декоратор (Decorator) — добавление функционала кофе

// Базовый интерфейс для напитка
public interface ICoffee
{
    string GetDescription();
    double GetCost();
}

// Конкретный напиток — просто кофе
public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Обычный кофе";
    public double GetCost() => 50.0; // 50 рублей
}

// Базовый декоратор — содержит ссылку на оборачиваемый объект
public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee)
    {
        _coffee = coffee;
    }
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual double GetCost() => _coffee.GetCost();
}

// Конкретные декораторы — добавляют свои функции
public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", с молоком";
    public override double GetCost() => _coffee.GetCost() + 20.0;
}

public class SugarDecorator : CoffeeDecorator
{
    private int _sugarAmount; // количество сахара (кубиков)
    
    public SugarDecorator(ICoffee coffee, int sugarAmount = 1) : base(coffee)
    {
        _sugarAmount = sugarAmount;
    }
    
    public override string GetDescription() => 
        $"{_coffee.GetDescription()}, с {_sugarAmount} кубик(а/ов) сахара";
    public override double GetCost() => _coffee.GetCost() + 5.0 * _sugarAmount;
}

public class WhippedCreamDecorator : CoffeeDecorator
{
    public WhippedCreamDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", со взбитыми сливками";
    public override double GetCost() => _coffee.GetCost() + 30.0;
}

// Демонстрация
class Program
{
    static void Main()
    {
        // Начинаем с простого кофе
        ICoffee coffee = new SimpleCoffee();
        Console.WriteLine($"{coffee.GetDescription()}: {coffee.GetCost()} руб.");
        
        // Добавляем молоко
        coffee = new MilkDecorator(coffee);
        Console.WriteLine($"{coffee.GetDescription()}: {coffee.GetCost()} руб.");
        
        // Добавляем сахар
        coffee = new SugarDecorator(coffee, 2);
        Console.WriteLine($"{coffee.GetDescription()}: {coffee.GetCost()} руб.");
        
        // Добавляем взбитые сливки
        coffee = new WhippedCreamDecorator(coffee);
        Console.WriteLine($"{coffee.GetDescription()}: {coffee.GetCost()} руб.");
        
        // Можно создать сложный заказ сразу:
        Console.WriteLine("
--- Сложный заказ сразу ---");
        ICoffee specialCoffee = new WhippedCreamDecorator(
            new SugarDecorator(
                new MilkDecorator(
                    new SimpleCoffee()
                ), 3
            )
        );
        Console.WriteLine($"{specialCoffee.GetDescription()}: {specialCoffee.GetCost()} руб.");
    }
}

Сравнение с Python: чем похоже и чем отличается

Паттерны проектирования — это концепция, которая не зависит от языка программирования. Вы можете использовать их и в C#, и в Python, и в Java, и в JavaScript. Но реализация будет отличаться из-за особенностей языка.

Аспект C# (.NET) Python
Типизация Строгая статическая — все типы известны на этапе компиляции Динамическая — тип определяется во время выполнения
Интерфейсы Явные интерфейсы (ключевое слово interface) Неявные (duck typing) — «если это крякает как утка, значит это утка»
Singleton Нужно писать вручную или использовать Lazy<T> Можно использовать модуль (модуль в Python — это Singleton по умолчанию)
Декоратор Реализуется через классы и наследование Есть встроенный синтаксис @decorator для функций
Наблюдатель Нужно писать свою реализацию Есть встроенные события (observers) через библиотеки
Производительность Выше (компилируемый язык) Ниже (интерпретируемый язык)
Сложность кода Больше boilerplate-кода (шаблонного) Меньше кода, более лаконичный

Пример Singleton на Python для сравнения:

class Singleton:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            print("Создан новый экземпляр")
        return cls._instance

# Использование:
# s1 = Singleton()
# s2 = Singleton()
# print(s1 is s2)  # True — это один и тот же объект
Важно: В Python многие паттерны реализуются проще и короче, но это не значит, что они не нужны. Просто Python берёт на себя часть работы «под капотом». В C# вы контролируете всё вручную, что даёт больше гибкости и производительности.

Типичные ошибки и подводные камни

1. «Золотой молоток» — использование паттернов везде

Са