Lapa Knowledge Base
Поведенческие паттерны

Chain of Responsibility

Паттерн Chain of Responsibility

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

Проблема

Представьте, что вы создаете систему обработки заказов. Заказы могут требовать различной обработки в зависимости от их типа и суммы:

  • Заказы на сумму менее 1000 руб. обрабатываются менеджером
  • Заказы на сумму от 1000 до 5000 руб. обрабатываются старшим менеджером
  • Заказы на сумму свыше 5000 руб. обрабатываются директором

Если вы реализуете это с помощью условных операторов, код станет сложным и трудно расширяемым.

Решение

Паттерн Chain of Responsibility предлагает связать обработчики в цепочку и передавать запрос по цепочке, пока один из обработчиков не обработает его.


Структура

Handler
├── nextHandler: Handler
├── setNext(handler: Handler): Handler
└── handle(request: Request): void

ConcreteHandlerA
├── nextHandler: Handler
├── setNext(handler: Handler): Handler
└── handle(request: Request): void

ConcreteHandlerB
├── nextHandler: Handler
├── setNext(handler: Handler): Handler
└── handle(request: Request): void

Пример реализации

JavaScript

// Базовый обработчик
class Handler {
    constructor() {
        this.nextHandler = null;
    }
    
    setNext(handler) {
        this.nextHandler = handler;
        return handler;
    }
    
    handle(request) {
        if (this.nextHandler) {
            return this.nextHandler.handle(request);
        }
        return null;
    }
}

// Конкретные обработчики - уровни поддержки
class Level1Support extends Handler {
    handle(request) {
        if (request.level <= 1) {
            console.log(`Уровень 1 поддержки обрабатывает: ${request.description}`);
            return `Проблема решена на уровне 1: ${request.description}`;
        }
        return super.handle(request);
    }
}

class Level2Support extends Handler {
    handle(request) {
        if (request.level <= 2) {
            console.log(`Уровень 2 поддержки обрабатывает: ${request.description}`);
            return `Проблема решена на уровне 2: ${request.description}`;
        }
        return super.handle(request);
    }
}

class Level3Support extends Handler {
    handle(request) {
        if (request.level <= 3) {
            console.log(`Уровень 3 поддержки обрабатывает: ${request.description}`);
            return `Проблема решена на уровне 3: ${request.description}`;
        }
        return super.handle(request);
    }
}

class ManagerSupport extends Handler {
    handle(request) {
        console.log(`Менеджер поддержки обрабатывает: ${request.description}`);
        return `Проблема эскалирована менеджеру: ${request.description}`;
    }
}

// Использование
const level1 = new Level1Support();
const level2 = new Level2Support();
const level3 = new Level3Support();
const manager = new ManagerSupport();

// Строим цепочку
level1.setNext(level2).setNext(level3).setNext(manager);

console.log('=== Система поддержки клиентов ===');

// Создаем запросы разной сложности
const requests = [
    { level: 1, description: 'Не работает кнопка входа' },
    { level: 2, description: 'Ошибка при загрузке файла' },
    { level: 3, description: 'Проблема с базой данных' },
    { level: 4, description: 'Критическая ошибка системы' }
];

// Обрабатываем запросы
requests.forEach(request => {
    console.log(`\n--- Обработка запроса уровня ${request.level} ---`);
    const result = level1.handle(request);
    console.log(`Результат: ${result}`);
});

TypeScript с интерфейсами

// Интерфейс обработчика
interface Handler {
    setNext(handler: Handler): Handler;
    handle(request: Request): string | null;
}

// Интерфейс запроса
interface Request {
    amount: number;
    description: string;
    type: string;
}

// Базовый обработчик
abstract class BaseHandler implements Handler {
    private nextHandler: Handler | null = null;
    
    setNext(handler: Handler): Handler {
        this.nextHandler = handler;
        return handler;
    }
    
    handle(request: Request): string | null {
        if (this.nextHandler) {
            return this.nextHandler.handle(request);
        }
        return null;
    }
}

// Конкретные обработчики - уровни авторизации
class ManagerHandler extends BaseHandler {
    handle(request: Request): string | null {
        if (request.amount <= 1000) {
            console.log(`Менеджер обрабатывает заказ: ${request.description}`);
            return `Заказ одобрен менеджером: ${request.description}`;
        }
        return super.handle(request);
    }
}

class SeniorManagerHandler extends BaseHandler {
    handle(request: Request): string | null {
        if (request.amount <= 5000) {
            console.log(`Старший менеджер обрабатывает заказ: ${request.description}`);
            return `Заказ одобрен старшим менеджером: ${request.description}`;
        }
        return super.handle(request);
    }
}

class DirectorHandler extends BaseHandler {
    handle(request: Request): string | null {
        if (request.amount <= 10000) {
            console.log(`Директор обрабатывает заказ: ${request.description}`);
            return `Заказ одобрен директором: ${request.description}`;
        }
        return super.handle(request);
    }
}

class BoardHandler extends BaseHandler {
    handle(request: Request): string | null {
        console.log(`Совет директоров обрабатывает заказ: ${request.description}`);
        return `Заказ одобрен советом директоров: ${request.description}`;
    }
}

// Использование
const manager = new ManagerHandler();
const seniorManager = new SeniorManagerHandler();
const director = new DirectorHandler();
const board = new BoardHandler();

// Строим цепочку
manager.setNext(seniorManager).setNext(director).setNext(board);

console.log('=== Система авторизации заказов ===');

// Создаем заказы разной стоимости
const orders: Request[] = [
    { amount: 500, description: 'Офисные принадлежности', type: 'office' },
    { amount: 2500, description: 'Компьютерное оборудование', type: 'equipment' },
    { amount: 7500, description: 'Корпоративное ПО', type: 'software' },
    { amount: 15000, description: 'Серверное оборудование', type: 'hardware' }
];

// Обрабатываем заказы
orders.forEach(order => {
    console.log(`\n--- Обработка заказа на ${order.amount} руб. ---`);
    const result = manager.handle(order);
    console.log(`Результат: ${result}`);
});

Когда использовать

Используйте Chain of Responsibility, когда:

  • Нужно обрабатывать запросы несколькими обработчиками
  • Порядок обработчиков может изменяться
  • Нужно динамически добавлять или удалять обработчики
  • Неизвестно заранее, какой обработчик должен обработать запрос

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

  • Контролирует порядок обработки запросов
  • Принцип единственной ответственности
  • Принцип открытости/закрытости: можно добавлять новые обработчики
  • Уменьшает связанность между отправителем и получателем

Недостатки

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

Отличия от других паттернов

  • Command — инкапсулирует запросы как объекты
  • Mediator — централизует взаимодействие между объектами
  • Observer — уведомляет множество объектов об изменениях

Реальный пример

// Chain of Responsibility для валидации формы
class ValidationHandler {
    constructor() {
        this.nextHandler = null;
    }
    
    setNext(handler) {
        this.nextHandler = handler;
        return handler;
    }
    
    handle(field, value) {
        if (this.nextHandler) {
            return this.nextHandler.handle(field, value);
        }
        return { isValid: true, errors: [] };
    }
}

class RequiredValidationHandler extends ValidationHandler {
    handle(field, value) {
        if (!value || value.trim() === '') {
            return { 
                isValid: false, 
                errors: [`Поле ${field} обязательно для заполнения`] 
            };
        }
        return super.handle(field, value);
    }
}

class EmailValidationHandler extends ValidationHandler {
    handle(field, value) {
        if (field === 'email' && value) {
            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            if (!emailRegex.test(value)) {
                return { 
                    isValid: false, 
                    errors: [`Некорректный формат email`] 
                };
            }
        }
        return super.handle(field, value);
    }
}

class LengthValidationHandler extends ValidationHandler {
    handle(field, value) {
        if (field === 'password' && value && value.length < 8) {
            return { 
                isValid: false, 
                errors: [`Пароль должен содержать минимум 8 символов`] 
            };
        }
        return super.handle(field, value);
    }
}

class NumberValidationHandler extends ValidationHandler {
    handle(field, value) {
        if (field === 'age' && value && (isNaN(value) || value < 0 || value > 120)) {
            return { 
                isValid: false, 
                errors: [`Возраст должен быть числом от 0 до 120`] 
            };
        }
        return super.handle(field, value);
    }
}

// Использование
const requiredValidator = new RequiredValidationHandler();
const emailValidator = new EmailValidationHandler();
const lengthValidator = new LengthValidationHandler();
const numberValidator = new NumberValidationHandler();

// Строим цепочку валидации
requiredValidator
    .setNext(emailValidator)
    .setNext(lengthValidator)
    .setNext(numberValidator);

console.log('=== Система валидации формы ===');

// Тестируем валидацию
const testCases = [
    { field: 'name', value: 'John' },
    { field: 'email', value: 'john@example.com' },
    { field: 'password', value: 'password123' },
    { field: 'age', value: '25' },
    { field: 'name', value: '' }, // Пустое поле
    { field: 'email', value: 'invalid-email' }, // Неверный email
    { field: 'password', value: '123' }, // Короткий пароль
    { field: 'age', value: '150' } // Неверный возраст
];

testCases.forEach(testCase => {
    console.log(`\n--- Валидация поля ${testCase.field}: "${testCase.value}" ---`);
    const result = requiredValidator.handle(testCase.field, testCase.value);
    console.log(`Результат: ${result.isValid ? 'Валидно' : 'Невалидно'}`);
    if (!result.isValid) {
        console.log(`Ошибки: ${result.errors.join(', ')}`);
    }
});

Chain of Responsibility — это передача запросов по цепочке!

Copyright © 2026