Patrón Chain of Responsibility
Pasa solicitudes a lo largo de una cadena de manejadores hasta que uno la procese. Un patrón de comportamiento para desacoplar emisores y receptores.
Visión General
El Patrón Chain of Responsibility es un patrón de diseño de comportamiento que te permite pasar solicitudes a lo largo de una cadena de manejadores. Cada manejador decide si procesa la solicitud o la pasa al siguiente manejador en la cadena. Esto desacopla emisores de receptores y permite que múltiples objetos manejen una solicitud sin que el emisor sepa cuál lo hará.
Cuándo Usarlo
Usa el Patrón Chain of Responsibility cuando:
- Más de un objeto puede manejar una solicitud, y el manejador no se conoce de antemano
- Quieres emitir una solicitud a uno de varios objetos sin especificar el receptor explícitamente
- El conjunto de objetos que pueden manejar una solicitud debe especificarse dinámicamente
- Necesitas un pipeline o middleware donde cada paso pueda procesar, transformar o detener una solicitud
Solución
Python
from abc import ABC, abstractmethod
from typing import Optional
class Handler(ABC):
def __init__(self):
self._next: Optional['Handler'] = None
def set_next(self, handler: 'Handler') -> 'Handler':
self._next = handler
return handler # Habilita encadenamiento fluido
@abstractmethod
def handle(self, request: str) -> Optional[str]:
pass
def _pass_to_next(self, request: str) -> Optional[str]:
if self._next:
return self._next.handle(request)
return None
class AuthHandler(Handler):
def handle(self, request: str) -> Optional[str]:
if not request.startswith("token:"):
return "401 No Autorizado"
return self._pass_to_next(request)
class RateLimitHandler(Handler):
def __init__(self):
super().__init__()
self.requests = 0
self.limit = 3
def handle(self, request: str) -> Optional[str]:
self.requests += 1
if self.requests > self.limit:
return "429 Demasiadas Solicitudes"
return self._pass_to_next(request)
class DataHandler(Handler):
def handle(self, request: str) -> Optional[str]:
return f"Procesado: {request}"
# Construir la cadena
handler = AuthHandler()
handler.set_next(RateLimitHandler()).set_next(DataHandler())
print(handler.handle("token:abc123")) # Procesado
print(handler.handle("bad-request")) # 401 No Autorizado
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 AuthHandler extends Handler {
handle(request) {
if (!request.startsWith("token:")) {
return "401 No Autorizado";
}
return super.handle(request);
}
}
class RateLimitHandler extends Handler {
constructor() {
super();
this.requests = 0;
this.limit = 3;
}
handle(request) {
this.requests++;
if (this.requests > this.limit) {
return "429 Demasiadas Solicitudes";
}
return super.handle(request);
}
}
class DataHandler extends Handler {
handle(request) {
return `Procesado: ${request}`;
}
}
// Construir la cadena
const handler = new AuthHandler();
handler.setNext(new RateLimitHandler()).setNext(new DataHandler());
console.log(handler.handle("token:abc123")); // Procesado
console.log(handler.handle("bad-request")); // 401
Java
public abstract class Handler {
protected Handler next;
public Handler setNext(Handler next) {
this.next = next;
return next;
}
public abstract String handle(String request);
protected String passToNext(String request) {
if (next != null) {
return next.handle(request);
}
return null;
}
}
public class AuthHandler extends Handler {
@Override
public String handle(String request) {
if (!request.startsWith("token:")) {
return "401 No Autorizado";
}
return passToNext(request);
}
}
public class RateLimitHandler extends Handler {
private int requests = 0;
private final int limit = 3;
@Override
public String handle(String request) {
requests++;
if (requests > limit) {
return "429 Demasiadas Solicitudes";
}
return passToNext(request);
}
}
public class DataHandler extends Handler {
@Override
public String handle(String request) {
return "Procesado: " + request;
}
}
// Construir la cadena
Handler handler = new AuthHandler();
handler.setNext(new RateLimitHandler()).setNext(new DataHandler());
System.out.println(handler.handle("token:abc")); // Procesado
System.out.println(handler.handle("bad")); // 401
Explicación
El Patrón Chain of Responsibility tiene dos roles:
- Interfaz de Manejador — declara un método
handle()y mantiene una referencia al siguiente manejador - Manejadores Concretos — implementan lógica de procesamiento; cada uno decide si maneja la solicitud o la pasa adelante
El cliente construye el orden de la cadena. Cada manejador también puede elegir detener la cadena (cortocircuito) retornando temprano sin llamar al siguiente manejador.
Variantes
| Variante | Estructura | Ideal Para |
|---|---|---|
| Cadena Lineal | Lista enlazada de manejadores | Procesamiento secuencial simple |
| Cadena en Árbol | Manejadores organizados jerárquicamente | Árboles de decisión multinivel |
| Pipeline de Middleware | Array de funciones, cada una llama next() | Frameworks web (Express, Django middleware) |
| Bus de Eventos | Manejadores se registran para eventos específicos | Sistemas desacoplados orientados a eventos |
Lo que funciona
- Mantén los manejadores enfocados — cada manejador debe hacer una sola cosa (auth, validación, logging, etc.)
- Proporciona un manejador por defecto al final de la cadena para evitar solicitudes no manejadas
- Permite modificación de cadena en tiempo de ejecución exponiendo métodos
setNext()oaddHandler() - Usa objetos de solicitud inmutables para que los manejadores no modifiquen estado compartido accidentalmente
- Considera el orden cuidadosamente — los manejadores que cortocircuitan (auth, rate limiting) deben ir primero
Errores Comunes
- Crear cadenas circulares donde un manejador eventualmente se llama a sí mismo, causando bucles infinitos
- Olvidar llamar al siguiente manejador, descartando silenciosamente solicitudes que deberían haberse procesado
- Colocar manejadores lentos o bloqueantes temprano en la cadena, causando latencia innecesaria para solicitudes rechazadas
- Almacenar estado mutable en manejadores que se reutilizan entre solicitudes, causando contaminación cruzada
- Construir cadenas excesivamente largas que se vuelven difíciles de depurar
- No proporcionar un manejador por defecto al final de la cadena, dejando solicitudes sin manejar
- Mezclar preocupaciones dentro de un solo manejador en lugar de mantenerlos enfocados
- No documentar dependencias de manejadores y orden esperado
- Fallar al manejar excepciones en manejadores, causando que toda la cadena falle
- Usar el patrón de cadena cuando un condicional simple sería suficiente
Mejores Prácticas
-
Mantén los manejadores de responsabilidad única. Cada manejador debe manejar una preocupación específica (auth, validación, logging, etc.) para mantener claridad y testabilidad.
-
Proporciona un manejador por defecto. Siempre incluye un manejador comodín al final de la cadena para manejar solicitudes que caen a través, previniendo fallos silenciosos.
-
Documenta el orden de manejadores. Documenta claramente el orden esperado de manejadores y cualquier dependencia entre ellos, ya que el orden afecta considerablemente el comportamiento.
-
Usa objetos de solicitud inmutables. Pasa objetos de solicitud inmutables a través de la cadena para prevenir que los manejadores modifiquen accidentalmente estado compartido.
-
Maneja excepciones gracefulmente. Cada manejador debería capturar y manejar sus propias excepciones, o envolverlas apropiadamente para prevenir fallo de cadena.
-
Considera el impacto de rendimiento. Coloca manejadores rápidos de cortocircuito (auth, rate limiting) temprano en la cadena para fallar rápido y evitar procesamiento innecesario.
-
Soporta reconfiguración de cadena. Permite que los manejadores se añadan, eliminen o reordenen en tiempo de ejecución para flexibilidad.
-
Añade logging y monitoreo. Incluye manejadores de logging para trazar el flujo de solicitudes a través de la cadena e identificar cuellos de botella o fallos.
-
Evita referencias circulares. Asegura que la estructura de cadena sea acíclica para prevenir bucles infinitos durante el procesamiento de solicitudes.
-
Prueba manejadores en aislamiento. Escribe pruebas unitarias para cada manejador independientemente, luego pruebas de integración para la cadena completa.
Preguntas frecuentes
¿Es lo mismo que el middleware en frameworks web?
Sí. El middleware de Express.js, Django y ASP.NET Core son implementaciones del Patrón Chain of Responsibility. Cada middleware decide si procesa la solicitud, la pasa adelante, o cortocircuita con una respuesta.
¿Qué pasa si ningún manejador procesa la solicitud?
Por defecto, la solicitud cae a través de la cadena sin ser manejada. Considera agregar un Decorator para enriquecimiento o un manejador por defecto. Deberías agregar un manejador comodín al final que retorne una respuesta por defecto o lance un error apropiado.
¿Los manejadores pueden modificar la solicitud antes de pasarla?
Sí. A diferencia de una cadena pura de pasar-o-fallar, los manejadores pueden transformar, enriquecer o validar la solicitud antes de reenviarla. Esto es común en pipelines de middleware.
¿Cómo manejo operaciones asíncronas en una cadena?
Usa patrones async/await donde cada manejador retorna una promesa. La cadena espera la finalización de cada manejador antes de pasar al siguiente. Esto es esencial para operaciones I/O-bound como consultas de base de datos o llamadas API.
¿Debería usar este patrón para manejo de eventos?
Sí. Los sistemas de eventos a menudo usan una variación de este patrón donde múltiples event listeners (manejadores) pueden procesar un evento. A diferencia de una cadena lineal, los sistemas de eventos típicamente broadcastean a todos los manejadores registrados.
¿Cómo añado logging para trazar el flujo de solicitudes?
Añade un manejador de logging que registra la solicitud antes y después de cada manejador. Incluye timestamps y nombres de manejador para trazar el path de ejecución e identificar cuellos de botella de rendimiento.
¿Los manejadores pueden tener efectos secundarios?
Sí, los manejadores pueden realizar efectos secundarios como logging, colección de métricas, o actualizaciones de base de datos. Sin embargo, mantén los efectos secundarios enfocados y documentalos claramente para evitar comportamiento inesperado.
¿Cómo manejo escenarios de timeout en una cadena?
Implementa middleware de timeout que rastrea la duración de la solicitud y cortocircuita si el procesamiento excede un umbral. Esto previene que manejadores lentos bloqueen la cadena indefinidamente.
¿Deberían los manejadores ser sin estado?
Idealmente sí. Los manejadores sin estado son más fáciles de probar y reutilizar. Si el estado es necesario, asegúrate que esté scoped a la solicitud (almacenado en el objeto de contexto) en lugar de scoped al manejador para evitar contaminación cross-request.
¿Cómo implemento circuit breaking en una cadena?
Añade un manejador de circuit breaker que rastrea tasas de fallo y cortocircuita solicitudes cuando un servicio downstream está fallando. Esto previene fallos en cascada y mejora la resiliencia del sistema.
Recursos Relacionados
Patrón Command
Encapsula una petición como un objeto, permitiendo parametrizar clientes con colas, logs y operaciones deshacibles. Patrón de diseño conductual.
PatternPatrón Decorator
Añade nueva funcionalidad a objetos dinámicamente envolviéndolos. Patrón de diseño estructural para extensión flexible de comportamiento.
PatternPatrón Strategy
Define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Patrón de diseño conductual para selección flexible de comportamiento.
PatternPatrón Interpreter
Define una representación para la gramática de un lenguaje junto con un intérprete que usa la representación para interpretar oraciones. Un patrón de diseño de comportamiento para mini-lenguajes.
PatternPatrón Memento
Captura y restaura el estado interno de un objeto sin violar el encapsulamiento. Un patrón de comportamiento para deshacer/rehacer.
PatternPatrón Null Object
Usa un objeto por defecto en lugar de referencias null para eliminar verificaciones de null y simplificar el código cliente. Un patrón behavioral para defaults más seguros.