Что такое шаблон проектирования Chain of Responsibility (Цепочка обязанностей)?

Ответ

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

Ключевые компоненты:

  1. Handler (Обработчик): Определяет интерфейс для обработки запросов. Часто содержит ссылку на следующий обработчик.
  2. ConcreteHandler (Конкретный обработчик): Реализует логику обработки. Решает, обрабатывать ли запрос, и/или передаёт его дальше.
  3. Client (Клиент): Инициирует запрос к цепочке.

Пример реализации на Java:

// Абстрактный базовый обработчик
abstract class Logger {
    protected LogLevel level;
    protected Logger next; // Ссылка на следующий в цепи

    public Logger(LogLevel level) { this.level = level; }

    public void setNext(Logger next) { this.next = next; }

    public void logMessage(LogLevel severity, String message) {
        if (this.level.ordinal() <= severity.ordinal()) {
            write(message); // Обрабатываем, если наш уровень подходит
        }
        if (next != null) {
            next.logMessage(severity, message); // Передаём дальше
        }
    }
    abstract protected void write(String message);
}

// Конкретный обработчик
class ConsoleLogger extends Logger {
    public ConsoleLogger(LogLevel level) { super(level); }
    protected void write(String message) {
        System.out.println("[CONSOLE]: " + message);
    }
}

// Использование
Logger chain = new ConsoleLogger(LogLevel.INFO);
chain.setNext(new FileLogger(LogLevel.DEBUG));
chain.logMessage(LogLevel.ERROR, "System failure!"); // Выведет все логгеры

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

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

Недостатки:

  • Нет гарантии, что запрос будет обработан.
  • Может привести к неэффективности при длинных цепочках.

Ответ 18+ 🔞

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

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

Из чего состоит эта пиздопроебибна:

  1. Обработчик (Handler): Ну типа главный по тарелкам. Объявляет: «Ребята, я могу что-то делать, а могу и передать дальше». Часто тащит с собой ссылку на следующего бедолагу в цепи.
  2. Конкретный обработчик (ConcreteHandler): Это уже работяга. Он реально смотрит на запрос и решает: «А, это уровень ERROR, а я только для INFO, так что... следующий!» Или наоборот: «Так, ERROR? Это ж моя тема, щас всё порешаю!».
  3. Клиент (Client): Это ты, инициатор. Ты создаёшь эту цепь из страдальцев и кидаешь в неё первый камень — то бишь запрос.

Смотри, как это выглядит в коде, на примере системы логирования (всё честно, термины не трогаю):

// Абстрактный предок всех наших логгеров-страдальцев
abstract class Logger {
    protected LogLevel level; // Какой уровень тревоги он ловит
    protected Logger next; // А это — ссылка на следующего мудака в цепи. Классика!

    public Logger(LogLevel level) { this.level = level; }

    // Метод, чтобы прицепить следующего
    public void setNext(Logger next) { this.next = next; }

    // Главный метод — "обработай или передай дальше"
    public void logMessage(LogLevel severity, String message) {
        // Если наш уровень позволяет обработать эту тяжесть...
        if (this.level.ordinal() <= severity.ordinal()) {
            write(message); // ...то обрабатываем!
        }
        // А потом, ВНЕЗАПНО, пихаем запрос дальше, если есть кому.
        if (next != null) {
            next.logMessage(severity, message);
        }
    }
    // А это каждый сам реализует — КАК именно писать.
    abstract protected void write(String message);
}

// Конкретный чувак — пишет в консоль
class ConsoleLogger extends Logger {
    public ConsoleLogger(LogLevel level) { super(level); }
    protected void write(String message) {
        System.out.println("[CONSOLE ЛОГГЕР, БЛЯДЬ]: " + message);
    }
}

// А это — другой, пишет в файл
class FileLogger extends Logger {
    public FileLogger(LogLevel level) { super(level); }
    protected void write(String message) {
        // ...тут код записи в файл, но мне лень
        System.out.println("[ФАЙЛОВЫЙ ЛОГГЕР, СУКА]: " + message);
    }
}

// Как этим пользоваться? Да легко!
public class Main {
    public static void main(String[] args) {
        // Строим цепь: сначала консольный (ловит INFO и выше), за ним файловый (ловит DEBUG и выше)
        Logger consoleLogger = new ConsoleLogger(LogLevel.INFO);
        Logger fileLogger = new FileLogger(LogLevel.DEBUG);

        consoleLogger.setNext(fileLogger); // Цепляем файловый за консольным

        // Кидаем запрос уровня ERROR
        consoleLogger.logMessage(LogLevel.ERROR, "System failure! Всё пропало!");
        // И что будет? А будет: консольник скажет "моё!" и выведет.
        // Потом он передаст файловику, а тот тоже скажет "моё!" (DEBUG <= ERROR) и тоже выведет.
        // Вот тебе и цепочка, хитрая жопа!
    }
}

Почему это иногда охуенно:

  • Слабая связность: Тот, кто кидает запрос, нихуя не знает, кто его в итоге обработает. Может, первый, а может, последний. Ему похуй.
  • Гибкость: Захотел добавить нового обработчика — впендюрил его в середину цепи, и всё. Никто даже не заметил.
  • Динамика: Цепь можно собирать и пересобирать на лету, как конструктор.

А где подвох, спросишь ты?

  • Нет гарантии: Запрос может пройти через всю цепь, как Муму по речке, и нихуя не быть обработанным. Все отказались.
  • Производительность: Если цепь длинная, как мои костыли, а запросов много, то можно проебать на передачах из рук в руки. Но это уже проблемы архитектора, который эту цепь выстроил, пидарас шерстяной.

Короче, паттерн мощный, когда нужно размазать ответственность между кучей объектов. Главное — не устроить из него бесконечный волейбол, а то так и до дедлока недалеко, в рот меня чих-пых!