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

Ответ

Паттерн 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.

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

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

Ответ 18+ 🔞

Давай разберём этот паттерн, а то звучит как какая-то секта, а на деле всё просто, как три копейки.

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

Вот смотри, живой пример из кода выше — система логирования. У нас есть три упыря:

  1. Консольный логгер (ConsoleLogger) — скромняга, пишет всё подряд, начиная с обычных информационных сообщений (Info).
  2. Файловый логгер (FileLogger) — уже построже. Беспокоить его стоит только с предупреждениями (Warning) и хуже. Инфошный мусор он в файл тащить не станет.
  3. Почтовый логгер (EmailLogger) — совсем параноик. Его поднимают только по тревоге, когда всё горит и критическая ошибка (Critical).

Суть в том, что мы их связываем в цепочку: Консоль -> Файл -> Почта. И кидаем запрос в первого.

  • «Приложение запущено» (уровень Info). Консольник говорит: «Моё!» — и пишет в консоль. Файльник смотрит: «Инфа? Да пошло оно нахуй, не мой уровень». Но цепочку не рвёт! Он тупо передаёт запрос дальше почтальону. Почтальон тоже игнорирует. Всё.
  • «Загрузка CPU высокая» (уровень Warning). Консольник: «Моё!» — пишет. Файльник: «О, warning! Моё!» — дописывает в файл. Почтальон: «Ещё не критично» — игнорирует, но передачу не прерывает.
  • «База данных недоступна!» (уровень Critical). А вот тут веселье. Консольник пишет, Файльник пишет, а Почтальон, получив запрос, орет: «ЁБАНАРОТ! КРИТИЧЕСКАЯ ОШИБКА!» — и симулирует отправку алерта.

Красота в чём? В том, что тому, кто кидает сообщение (в Main), вообще похуй, кто и как его обработает. Он кинул в первого — и забыл. А если завтра понадобится добавить, скажем, TelegramLogger, который будет слать сообщения в чат, ты просто создашь новый класс и встроишь его в цепочку между Файлом и Почтой. Ничего ломать не нужно. Гибко, модульно, а главное — каждый класс отвечает только за свою епархию.

Где это в жизни? Да везде!

  • Тот самый пайплайн middleware в ASP.NET Core — это и есть чистейшая цепочка обязанностей. Запрос пришёл, прошёлся по цепочке мидлварей (логирование, аутентификация, авторизация, роутинг), каждый сделал своё дело.
  • Валидация формы. Одна проверка на пустоту, вторая на формат email, третья на сложность пароля — все выстроены в цепь. Первая проверка упала — остальные могут даже не запускаться (это уже вариация).
  • Обработка событий в интерфейсах. Всплыло окошко — обработалось, не обработалось — событие всплывает дальше к родительскому элементу.

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