Поведенческие паттерны
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 — это передача запросов по цепочке!