7 min read
Te piden agregar validaciones a la creación de pedidos de tu e-commerce: primero verificar que haya stock, después revisar que no sea un posible fraude, y finalmente confirmar que el cliente tiene crédito disponible.
Empezás con tres if. Dos semanas después, producto pide una validación más para clientes corporativos. Un mes después, otra para pedidos internacionales. Y el método createOrder() se convierte en esto:
// ANTES: un método que conoce todas las reglas y su orden
public void createOrder(OrderRequest order) {
if (inventory.getStock(order.getProductId()) >= order.getQuantity()) {
if (!fraudService.isSuspicious(order)) {
if (creditService.hasCredit(order.getCustomerId(), order.getTotalAmount())) {
// ... guardar el pedido ...
} else {
throw new IllegalStateException("Crédito insuficiente");
}
} else {
throw new IllegalStateException("Pedido sospechoso");
}
} else {
throw new IllegalStateException("Stock insuficiente");
}
}
Cada regla nueva obliga a modificar este método, a reordenar los if con cuidado, y a probar todo de nuevo. El Patrón Cadena de Responsabilidad (Chain of Responsibility) resuelve exactamente esto, y es uno de los patrones clásicos de comportamiento que todavía no habíamos cubierto en el blog.
¿Qué es la Cadena de Responsabilidad?
Head First Design Patterns lo define así: el patrón Cadena de Responsabilidad evita acoplar el emisor de una solicitud a su receptor, dando a más de un objeto la oportunidad de manejarla. En español simple: en lugar de un método que conoce todos los pasos, armás una cadena de objetos, cada uno con una sola responsabilidad. La solicitud entra por el primero, y cada eslabón decide una de dos cosas: manejarla y cortar la cadena (por ejemplo, rechazando el pedido), o pasarla al siguiente.
Si alguna vez usaste un Filter de servlets o la cadena de filtros de Spring Security, ya usaste este patrón: cada filtro hace lo suyo y le pasa el request al siguiente.
El diagrama

Guía paso a paso
Paso 1 — Definir la clase base del eslabón
Una clase abstracta que guarda una referencia al siguiente eslabón y tiene dos responsabilidades: ejecutar su propia validación y decidir si delega.
Paso 2 — Crear un eslabón por cada regla
StockValidator, FraudValidator, CreditValidator. Cada uno hace una sola cosa, lo que conecta directo con el principio de Responsabilidad Única que vimos en el post de SOLID.
Paso 3 — Armar la cadena
Se conectan con setNext(). El orden importa: poné primero las validaciones rápidas (stock en memoria) y al final las lentas (llamadas a servicios externos).
Paso 4 — Enviar la solicitud al primer eslabón
El cliente solo conoce el primer eslabón. No sabe cuántas reglas hay detrás ni en qué orden se ejecutan.
Paso 5 — Agregar reglas nuevas sin tocar las existentes
Una regla nueva es una clase nueva que se inserta en la cadena. Ningún eslabón existente se modifica, lo que cumple el principio Abierto/Cerrado.
El código
Implementamos la cadena de validación de pedidos con Java y Lombok para mantener las clases de datos pequeñas.
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Map;
// --- Datos del pedido y resultado de la validación ---
@Getter
@AllArgsConstructor
class OrderRequest {
private final String orderId;
private final String customerId;
private final String productId;
private final int quantity;
private final double totalAmount;
}
@Getter
@AllArgsConstructor
class ValidationResult {
private final boolean approved;
private final String reason;
static ValidationResult ok() {
return new ValidationResult(true, "OK");
}
static ValidationResult rejected(String reason) {
return new ValidationResult(false, reason);
}
}
// --- El eslabón base de la cadena ---
abstract class OrderValidator {
private OrderValidator next;
public OrderValidator setNext(OrderValidator next) {
this.next = next;
return next; // permite encadenar: a.setNext(b).setNext(c)
}
public ValidationResult validate(OrderRequest order) {
ValidationResult result = check(order);
if (!result.isApproved() || next == null) {
return result; // rechazado o fin de la cadena
}
return next.validate(order); // delega en el siguiente eslabón
}
protected abstract ValidationResult check(OrderRequest order);
}
// --- Eslabón 1: ¿hay stock? ---
@AllArgsConstructor
class StockValidator extends OrderValidator {
private final Map<String, Integer> inventory;
@Override
protected ValidationResult check(OrderRequest order) {
int available = inventory.getOrDefault(order.getProductId(), 0);
if (available < order.getQuantity()) {
return ValidationResult.rejected(
"Stock insuficiente: pidió " + order.getQuantity() + ", hay " + available);
}
return ValidationResult.ok();
}
}
// --- Eslabón 2: ¿patrón sospechoso? ---
@AllArgsConstructor
class FraudValidator extends OrderValidator {
private static final int MAX_ORDERS_PER_HOUR = 3;
private final Map<String, Integer> ordersLastHour;
@Override
protected ValidationResult check(OrderRequest order) {
int recent = ordersLastHour.getOrDefault(order.getCustomerId(), 0);
if (recent >= MAX_ORDERS_PER_HOUR) {
return ValidationResult.rejected(
"Demasiados pedidos en la última hora, enviado a revisión antifraude");
}
return ValidationResult.ok();
}
}
// --- Eslabón 3: ¿tiene crédito? ---
@AllArgsConstructor
class CreditValidator extends OrderValidator {
private final Map<String, Double> availableCredit;
@Override
protected ValidationResult check(OrderRequest order) {
double credit = availableCredit.getOrDefault(order.getCustomerId(), 0.0);
if (credit < order.getTotalAmount()) {
return ValidationResult.rejected(
"Crédito insuficiente: disponible $" + credit + ", requerido $" + order.getTotalAmount());
}
return ValidationResult.ok();
}
}
// --- Main ---
public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
Map<String, Integer> inventory = Map.of("PROD-1", 10, "PROD-2", 1);
Map<String, Integer> ordersLastHour = Map.of("CUST-2", 4);
Map<String, Double> credit = Map.of("CUST-1", 500.0, "CUST-2", 900.0, "CUST-3", 50.0);
// Armamos la cadena: stock → antifraude → crédito
OrderValidator chain = new StockValidator(inventory);
chain.setNext(new FraudValidator(ordersLastHour))
.setNext(new CreditValidator(credit));
OrderRequest[] orders = {
new OrderRequest("ORD-1", "CUST-1", "PROD-1", 2, 100.0),
new OrderRequest("ORD-2", "CUST-1", "PROD-2", 5, 250.0),
new OrderRequest("ORD-3", "CUST-2", "PROD-1", 1, 80.0),
new OrderRequest("ORD-4", "CUST-3", "PROD-1", 1, 300.0)
};
for (OrderRequest order : orders) {
ValidationResult result = chain.validate(order);
String status = result.isApproved() ? "APROBADA" : "RECHAZADA";
System.out.println(order.getOrderId() + " → " + status + " (" + result.getReason() + ")");
}
}
}
Output esperado:
ORD-1 → APROBADA (OK)
ORD-2 → RECHAZADA (Stock insuficiente: pidió 5, hay 1)
ORD-3 → RECHAZADA (Demasiados pedidos en la última hora, enviado a revisión antifraude)
ORD-4 → RECHAZADA (Crédito insuficiente: disponible $50.0, requerido $300.0)
Fijate que el cliente solo llamó a chain.validate(order). No sabe cuántos validadores hay ni en qué orden se ejecutan, y agregar uno nuevo no toca ninguna de las tres clases existentes.
Para tener en cuenta en producción
- El orden de la cadena es una decisión de rendimiento: poné primero las reglas rápidas y las que más pedidos rechazan, y al final las que llaman a servicios externos. Cada llamada de red evitada cuenta.
- Esta versión corta en el primer rechazo: si necesitás devolverle al usuario todos los errores a la vez (por ejemplo, validar un formulario), la cadena no es lo ideal. En ese caso recorré una lista de validadores y acumulá los resultados.
- Ya la usás sin saberlo: los filtros de servlets, la cadena de filtros de Spring Security y los interceptores de HTTP en muchos clientes son implementaciones de este patrón.
- Cuidado con las cadenas rotas: si un eslabón olvida delegar, o la cadena queda mal armada, las reglas siguientes nunca se ejecutan y el error pasa en silencio. Un test que arme la cadena real y pruebe un caso por regla te protege de eso.
Conclusión
La Cadena de Responsabilidad convierte un método lleno de condicionales anidados en una lista de piezas pequeñas, probables por separado y fáciles de reordenar. Es el patrón ideal cuando una solicitud tiene que pasar por varios pasos independientes y cada paso puede decidir detener el proceso.
Recordá suscribirte aquí para recibir los próximos posts directamente en tu correo.
Referencias:
Head First Design Patterns — Eric Freeman, Elisabeth Robson
Refactoring Guru — Chain of Responsibility: https://refactoring.guru/es/design-patterns/chain-of-responsibility