Приведи пример реализации паттерна «Цепочка обязанностей» (Chain of Responsibility)

«Приведи пример реализации паттерна «Цепочка обязанностей» (Chain of Responsibility)» — вопрос из категории Паттерны, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Паттерн Chain of Responsibility позволяет передавать запрос по цепочке обработчиков. Каждый обработчик решает, обработать ли запрос самому или передать дальше по цепочке.

Классический пример: Система логирования с разными уровнями серьезности.

// 1. Абстрактный базовый класс обработчика
public abstract class LoggerHandler
{
    protected LogLevel _level;
    protected LoggerHandler? _nextHandler; // Ссылка на следующий в цепочке

    public LoggerHandler(LogLevel level)
    {
        _level = level;
    }

    // Метод для установки следующего обработчика
    public LoggerHandler SetNext(LoggerHandler nextHandler)
    {
        _nextHandler = nextHandler;
        return nextHandler; // Позволяет строить цепочку fluent-стилем
    }

    // Основной метод обработки запроса
    public void LogMessage(LogLevel level, string message)
    {
        if (_level <= level) // Если уровень серьезности достаточен для этого обработчика
        {
            Write(message); // Обрабатываем
        }

        // Передаем запрос дальше по цепочке, независимо от того, обработали мы его или нет
        _nextHandler?.LogMessage(level, message);
    }

    protected abstract void Write(string message); // Конкретная реализация записи
}

// 2. Конкретные обработчики
public class ConsoleLogger : LoggerHandler
{
    public ConsoleLogger(LogLevel level) : base(level) { }

    protected override void Write(string message)
    {
        Console.WriteLine($"[CONSOLE] {DateTime.Now}: {message}");
    }
}

public class FileLogger : LoggerHandler
{
    private readonly string _filePath;
    public FileLogger(LogLevel level, string filePath) : base(level)
    {
        _filePath = filePath;
    }

    protected override void Write(string message)
    {
        File.AppendAllText(_filePath, $"[FILE] {DateTime.Now}: {message}" + Environment.NewLine);
    }
}

public class EmailLogger : LoggerHandler
{
    public EmailLogger(LogLevel level) : base(level) { }

    protected override void Write(string message)
    {
        // Симуляция отправки email только для критических ошибок
        Console.WriteLine($"[EMAIL ALERT] Критическая ошибка: {message}");
    }
}

// 3. Использование
public enum LogLevel { Info = 1, Warning = 2, Error = 3, Critical = 4 }

class Program
{
    static void Main()
    {
        // Построение цепочки: Console -> File -> Email
        var consoleLogger = new ConsoleLogger(LogLevel.Info);    // Логирует всё (Info и выше)
        var fileLogger = new FileLogger(LogLevel.Warning, "log.txt"); // Логирует Warning и выше
        var emailLogger = new EmailLogger(LogLevel.Critical);   // Логирует только Critical

        // Связывание обработчиков в цепочку
        consoleLogger.SetNext(fileLogger).SetNext(emailLogger);

        // Отправка сообщений в начало цепочки
        consoleLogger.LogMessage(LogLevel.Info, "Приложение запущено.");
        // Вывод: [CONSOLE] ... (File и Email проигнорируют Info)

        consoleLogger.LogMessage(LogLevel.Warning, "Загрузка CPU высокая.");
        // Вывод: [CONSOLE] ... и запись в файл (Email проигнорирует Warning)

        consoleLogger.LogMessage(LogLevel.Critical, "База данных недоступна!");
        // Вывод: [CONSOLE] ..., запись в файл И отправка email-алерта
    }
}

Где еще применяется:

  • Обработка HTTP-запросов в Middleware пайплайне ASP.NET Core (каждый middleware — звено цепи).
  • Валидация данных (цепочка проверок).
  • Обработка событий в UI.

Преимущества:

  • Уменьшает связанность: отправитель запроса не знает, кто его обработает.
  • Позволяет динамически менять цепочку.
  • Упрощает добавление новых обработчиков.