Паттерн «Цепочка ответственности» (Chain of Responsibility) в Python: гибкая обработка запросов

Паттерн «Цепочка ответственности» (Chain of Responsibility) в Python: гибкая обработка запросов


Паттерн «Цепочка ответственности» (Chain of Responsibility) в Python: гибкая обработка запросов

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


Проблема

Представьте, что вы разрабатываете систему обработки HTTP-запросов, где каждый запрос требует выполнения нескольких этапов проверки:

  1. Аутентификация пользователя.
  2. Проверка прав доступа.
  3. Валидация данных.
  4. Логирование действий.

Если реализовать все этапы в одном классе, код станет монолитным и сложным для изменения. Добавление новых проверок или изменение порядка их выполнения потребует переписывания логики, что нарушает принцип открытости/закрытости (Open/Closed Principle).


Решение: паттерн Chain of Responsibility

Паттерн предлагает разбить обработку на отдельные объекты-обработчики (Handlers), связанные в цепочку. Каждый обработчик:

  • Решает, может ли он обработать запрос.
  • Либо передает запрос следующему обработчику в цепочке.

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

Структура паттерна:

  1. Handler — интерфейс обработчика с методом handle() и ссылкой на следующий обработчик.
  2. ConcreteHandler — конкретные реализации обработчиков.
  3. Client — инициализирует цепочку и запускает обработку.

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

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

from abc import ABC, abstractmethod

class Handler(ABC):
    """Базовый интерфейс обработчика."""
    def __init__(self, next_handler=None):
        self._next_handler = next_handler

    @abstractmethod
    def handle(self, request: dict) -> str:
        pass

    def _pass_to_next(self, request: dict) -> str:
        """Передача запроса следующему обработчику."""
        if self._next_handler:
            return self._next_handler.handle(request)
        return "Запрос не обработан."


class AuthenticationHandler(Handler):
    """Проверка аутентификации пользователя."""
    def handle(self, request: dict) -> str:
        if not request.get("is_authenticated"):
            return "Ошибка: Пользователь не аутентифицирован."
        print("Аутентификация успешна.")
        return self._pass_to_next(request)


class AuthorizationHandler(Handler):
    """Проверка прав доступа."""
    def handle(self, request: dict) -> str:
        if request.get("user_role") != "admin":
            return "Ошибка: Недостаточно прав."
        print("Права доступа подтверждены.")
        return self._pass_to_next(request)


class ValidationHandler(Handler):
    """Валидация данных документа."""
    def handle(self, request: dict) -> str:
        if not request.get("document_id"):
            return "Ошибка: Документ не указан."
        print("Документ валиден.")
        return self._pass_to_next(request)


# Создание цепочки обработчиков
chain = AuthenticationHandler(
    AuthorizationHandler(
        ValidationHandler()
    )
)

# Пример запроса
request = {
    "is_authenticated": True,
    "user_role": "admin",
    "document_id": 12345
}

result = chain.handle(request)
print(f"Результат: {result}")

# Вывод:
# Аутентификация успешна.
# Права доступа подтверждены.
# Документ валиден.
# Результат: Запрос не обработан. (если нет следующего обработчика)

Преимущества и недостатки

Плюсы:

  • Уменьшает зависимость между клиентом и обработчиками.
  • Позволяет динамически менять цепочку или добавлять новые обработчики.
  • Реализует принцип единственной ответственности (Single Responsibility Principle).

Минусы:

  • Нет гарантии, что запрос будет обработан (если цепочка не завершена).
  • Может усложнить отладку из-за распределенной логики.

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

  • Обработка событий в GUI, где событие передается по цепочке виджетов.
  • Middleware в веб-фреймворках (например, Django, Flask).
  • Проверка прав доступа или многоэтапная валидация данных.
  • Логирование с разными уровнями детализации (info, warning, error).

Модификации

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

Заключение

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