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 este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
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.