← Назад к вопросам

Приведи пример паттерна по распределению обязанностей

2.3 Middle🔥 111 комментариев
#ООП и паттерны проектирования

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример распределения обязанностей с использованием паттерна Chain of Responsibility

Распределение обязанностей — фундаментальный принцип проектирования ПО, который помогает создавать поддерживаемые, гибкие и тестируемые системы. Одним из классических паттернов, прямо воплощающим этот принцип, является Chain of Responsibility (Цепочка обязанностей). Он позволяет передавать запросы по цепочке обработчиков, где каждый решает, обработать его или передать следующему.

Суть паттерна

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

  1. Обрабатывает только тот тип запроса, для которого предназначен.
  2. Имеет ссылку на следующий обработчик в цепочке.
  3. Принимает решение: обработать запрос самостоятельно или делегировать его дальше.

Практический пример: Валидация и обработка платежа

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

1. Определение базового абстрактного класса обработчика

public abstract class PaymentHandler
{
    protected PaymentHandler NextHandler;
    
    public void SetNext(PaymentHandler handler)
    {
        NextHandler = handler;
    }
    
    public abstract void Handle(PaymentRequest request);
}

public class PaymentRequest
{
    public decimal Amount { get; set; }
    public string Currency { get; set; }
    public string CardNumber { get; set; }
    public bool IsFraudDetected { get; set; }
}

2. Создание конкретных обработчиков (звеньев цепи)

Каждый обработчик отвечает за свою конкретную задачу:

// Валидация суммы
public class AmountValidationHandler : PaymentHandler
{
    public override void Handle(PaymentRequest request)
    {
        if (request.Amount <= 0)
        {
            throw new ArgumentException("Сумма платежа должна быть положительной");
        }
        
        if (request.Amount > 100000)
        {
            throw new InvalidOperationException("Превышен лимит на одну операцию");
        }
        
        // Передаём следующему обработчику
        NextHandler?.Handle(request);
    }
}

// Проверка валюты
public class CurrencyValidationHandler : PaymentHandler
{
    private readonly HashSet<string> _supportedCurrencies = new() { "USD", "EUR", "RUB" };
    
    public override void Handle(PaymentRequest request)
    {
        if (!_supportedCurrencies.Contains(request.Currency))
        {
            throw new ArgumentException($"Валюта {request.Currency} не поддерживается");
        }
        
        NextHandler?.Handle(request);
    }
}

// Обнаружение мошенничества
public class FraudDetectionHandler : PaymentHandler
{
    public override void Handle(PaymentRequest request)
    {
        if (request.IsFraudDetected)
        {
            // В реальной системе здесь была бы сложная логика анализа
            throw new SecurityException("Обнаружена подозрительная активность");
        }
        
        NextHandler?.Handle(request);
    }
}

// Непосредственная обработка платежа
public class PaymentProcessorHandler : PaymentHandler
{
    public override void Handle(PaymentRequest request)
    {
        // Здесь выполняется финальная обработка платежа
        Console.WriteLine($"Обработка платежа на {request.Amount} {request.Currency}");
        // Логика списания средств, взаимодействие с банком и т.д.
    }
}

3. Построение и использование цепочки

public class PaymentService
{
    private PaymentHandler _handlerChain;
    
    public PaymentService()
    {
        // Строим цепочку обработчиков
        var amountValidator = new AmountValidationHandler();
        var currencyValidator = new CurrencyValidationHandler();
        var fraudDetector = new FraudDetectionHandler();
        var processor = new PaymentProcessorHandler();
        
        amountValidator.SetNext(currencyValidator);
        currencyValidator.SetNext(fraudDetector);
        fraudDetector.SetNext(processor);
        
        _handlerChain = amountValidator;
    }
    
    public void ProcessPayment(PaymentRequest request)
    {
        try
        {
            _handlerChain.Handle(request);
            Console.WriteLine("Платеж успешно обработан");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Ошибка обработки платежа: {ex.Message}");
        }
    }
}

// Использование
var paymentService = new PaymentService();
var request = new PaymentRequest 
{ 
    Amount = 5000, 
    Currency = "USD", 
    CardNumber = "4111111111111111",
    IsFraudDetected = false 
};

paymentService.ProcessPayment(request);

Преимущества такого подхода к распределению обязанностей

Гибкость и расширяемость:

  • Новые проверки добавляются простым созданием нового класса-обработчика
  • Порядок обработчиков можно динамически менять без изменения их кода
  • Легко отключать или включать отдельные проверки

Принцип единой ответственности (SRP):

  • Каждый обработчик отвечает только за одну конкретную задачу
  • Код валидации суммы не смешан с обнаружением мошенничества

Упрощение тестирования:

  • Каждый обработчик можно тестировать изолированно
  • Мокирование зависимостей становится тривиальной задачей

Снижение связанности:

  • Обработчики ничего не знают друг о друге, кроме интерфейса следующего звена
  • Изменение одного обработчика не влияет на работу других

Альтернативные паттерны распределения обязанностей

  • Strategy — инкапсуляция алгоритмов в отдельные классы
  • Command — представление запросов в виде объектов
  • Decorator — динамическое добавление обязанностей объектам
  • Mediator — централизованное управление взаимодействием объектов

Chain of Responsibility особенно эффективен в сценариях, где требуется последовательная обработка с возможностью раннего прерывания цепи (как в случае с валидациями). В реальных C# приложениях этот паттерн часто встречается в middleware-конвейерах ASP.NET Core, где каждый компонент middleware является звеном цепи, обрабатывающим HTTP-запрос.