Цепочка ответственности

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

Валидация становится особенно сложной в прикладных JavaScript-системах, где данные могут поступать из различных источников: формы, API, внешних сервисов. Библиотека Validator.js предоставляет набор атомарных функций, которые легко комбинируются между собой, что делает её удобной основой для построения цепочек ответственности.


Принцип последовательной обработки

Суть цепочки ответственности в валидации заключается в том, что каждое правило проверки рассматривается как отдельное звено:

  • каждое звено получает входное значение;
  • выполняет строго одну проверку;
  • либо пропускает значение дальше;
  • либо завершает цепочку с ошибкой.

Такая структура исключает жёсткую связанность проверок и позволяет изменять порядок правил без переписывания логики.

Валидация email, например, может включать:

  1. Проверку на пустое значение
  2. Проверку формата строки
  3. Проверку длины
  4. Проверку доменной части

Каждый шаг может быть реализован как отдельный обработчик цепочки.


Validator.js как набор звеньев цепочки

Библиотека Validator.js предоставляет набор функций, каждая из которых может выступать отдельным звеном:

  • isEmail(value) — проверка email-формата
  • isLength(value, options) — проверка длины строки
  • isEmpty(value) — проверка на пустоту
  • isNumeric(value) — проверка числового значения
  • isAlphanumeric(value) — проверка символов

Каждая функция возвращает булево значение, что позволяет легко встроить их в цепочку обработки.


Базовая реализация цепочки

Цепочка ответственности в JavaScript может быть реализована через массив функций или через классическую структуру обработчиков.

Вариант на функциях

const validators = [
  (value) => {
    if (value == null || value === '') {
      return { valid: false, error: 'Значение пустое' };
    }
    return { valid: true, value };
  },

  (value) => {
    if (!validator.isEmail(value)) {
      return { valid: false, error: 'Некорректный email' };
    }
    return { valid: true, value };
  },

  (value) => {
    if (!validator.isLength(value, { min: 5 })) {
      return { valid: false, error: 'Слишком короткое значение' };
    }
    return { valid: true, value };
  }
];

function runChain(value) {
  return validators.reduce((result, validatorFn) => {
    if (!result.valid) return result;
    return validatorFn(result.value);
  }, { valid: true, value });
}

Объектная реализация цепочки

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

class Handler {
  setNext(handler) {
    this.next = handler;
    return handler;
  }

  handle(value) {
    if (this.next) {
      return this.next.handle(value);
    }
    return { valid: true, value };
  }
}

Каждый конкретный валидатор расширяет базовый обработчик:

class EmailValidator extends Handler {
  handle(value) {
    if (!validator.isEmail(value)) {
      return { valid: false, error: 'Неверный email' };
    }
    return super.handle(value);
  }
}

class LengthValidator extends Handler {
  handle(value) {
    if (!validator.isLength(value, { min: 5 })) {
      return { valid: false, error: 'Недостаточная длина' };
    }
    return super.handle(value);
  }
}

Формирование цепочки:

const email = new EmailValidator();
const length = new LengthValidator();

email.setNext(length);

email.handle('test@example.com');

Связь с функциональным стилем Validator.js

Библиотека Validator.js изначально ориентирована на функциональный стиль. Её функции не хранят состояние и не зависят друг от друга. Это делает её идеальной для построения цепочек:

  • каждая функция является чистой;
  • порядок вызовов полностью управляем;
  • можно динамически собирать цепочки.

Пример композиции:

function validateEmail(value) {
  if (validator.isEmpty(value)) {
    return { valid: false, error: 'Пустое значение' };
  }

  if (!validator.isEmail(value)) {
    return { valid: false, error: 'Неверный формат email' };
  }

  if (!validator.isLength(value, { max: 50 })) {
    return { valid: false, error: 'Слишком длинный email' };
  }

  return { valid: true, value };
}

Несмотря на отсутствие формальной структуры цепочки, логика остаётся последовательной.


Динамическое формирование цепочек

Одним из ключевых преимуществ подхода является возможность динамически собирать цепочку правил в зависимости от контекста.

function buildEmailChain({ strict }) {
  const chain = [];

  chain.push((v) => validator.isEmpty(v) ? { valid: false } : { valid: true, value: v });
  chain.push((v) => validator.isEmail(v) ? { valid: true, value: v } : { valid: false });

  if (strict) {
    chain.push((v) =>
      validator.isLength(v, { max: 30 })
        ? { valid: true, value: v }
        : { valid: false }
    );
  }

  return chain;
}

Обработка ошибок внутри цепочки

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

Немедленный выход

Цепочка прерывается при первой ошибке.

Накопление ошибок

Все ошибки собираются в массив:

const errors = [];

function validatorFn(value) {
  if (!validator.isEmail(value)) {
    errors.push('Email некорректен');
  }

  if (!validator.isLength(value, { min: 5 })) {
    errors.push('Слишком короткий email');
  }

  return {
    valid: errors.length === 0,
    errors
  };
}

Приоритизация правил

Некоторые проверки выполняются раньше других, формируя приоритетную цепочку.


Композиция цепочек и переиспользование

Цепочки можно объединять и переиспользовать как строительные блоки.

const baseChain = [
  (v) => validator.isEmpty(v) ? fail('empty') : pass(v),
];

const emailChain = [
  ...baseChain,
  (v) => validator.isEmail(v) ? pass(v) : fail('email'),
];

Такой подход уменьшает дублирование и упрощает поддержку системы валидации.


Интеграция с формами и API

В прикладных системах цепочка ответственности часто применяется при обработке данных формы:

function validateForm(data) {
  const result = {
    email: validateEmail(data.email),
    password: validatePassword(data.password),
    age: validateAge(data.age)
  };

  return result;
}

Каждое поле имеет собственную цепочку, но общая структура остаётся единообразной.


Расширяемость цепочек

Цепочка ответственности позволяет добавлять новые правила без изменения существующих:

  • добавление проверки CAPTCHA;
  • интеграция антиспам-фильтров;
  • подключение бизнес-логики;
  • условные проверки в зависимости от роли пользователя.
if (user.role === 'admin') {
  chain.push(adminSpecificValidator);
}

Ограничения подхода

Несмотря на гибкость, цепочка ответственности имеет особенности:

  • рост количества звеньев усложняет трассировку ошибок;
  • порядок выполнения становится критически важным;
  • избыточная декомпозиция может снижать читаемость;
  • в сложных сценариях требуется контроль состояния.

Роль Validator.js в построении цепочек

Библиотека Validator.js выступает не как система цепочек, а как набор атомарных проверок, из которых эти цепочки строятся. Её функции:

  • легко комбинируются;
  • предсказуемы;
  • не имеют побочных эффектов;
  • поддерживают широкий спектр проверок строковых данных.

Это делает её универсальным инструментом для реализации паттерна цепочки ответственности в JavaScript-приложениях различного масштаба.